[FEATURE] Support duplicate NFO keys (#220)

This commit is contained in:
Jesse Bannon 2022-09-07 14:06:13 -07:00 committed by GitHub
parent 6383d133dc
commit 819bddc9d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 320 additions and 99 deletions

View file

@ -28,6 +28,7 @@ from ytdl_sub.validators.string_formatter_validators import OverridesDictFormatt
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
from ytdl_sub.validators.validators import StringListValidator
from ytdl_sub.validators.validators import StringValidator
from ytdl_sub.validators.validators import Validator
@ -244,28 +245,30 @@ class Preset(StrictDictValidator):
def __recursive_preset_validate(
self,
validator_dict: Optional[Dict[str, Validator]] = None,
validator: Optional[Validator] = None,
) -> None:
"""
Ensure all OverridesStringFormatterValidator's only contain variables from the overrides
and resolve.
"""
if validator_dict is None:
validator_dict = self._validator_dict
if validator is None:
validator = self
for validator in validator_dict.values():
if isinstance(validator, DictValidator):
# pylint: disable=protected-access
# Usage of protected variables in other validators is fine. The reason to keep them
# protected is for readability when using them in subscriptions.
self.__recursive_preset_validate(validator._validator_dict)
# pylint: enable=protected-access
if isinstance(validator, (StringFormatterValidator, OverridesStringFormatterValidator)):
self.__validate_override_string_formatter_validator(validator)
if isinstance(validator, (DictFormatterValidator, OverridesDictFormatterValidator)):
for validator_value in validator.dict.values():
self.__validate_override_string_formatter_validator(validator_value)
if isinstance(validator, DictValidator):
# pylint: disable=protected-access
# Usage of protected variables in other validators is fine. The reason to keep
# them protected is for readability when using them in subscriptions.
for validator_value in validator._validator_dict.values():
self.__recursive_preset_validate(validator_value)
# pylint: enable=protected-access
elif isinstance(validator, ListValidator):
for list_value in validator.list:
self.__recursive_preset_validate(list_value)
elif isinstance(validator, (StringFormatterValidator, OverridesStringFormatterValidator)):
self.__validate_override_string_formatter_validator(validator)
elif isinstance(validator, (DictFormatterValidator, OverridesDictFormatterValidator)):
for validator_value in validator.dict.values():
self.__validate_override_string_formatter_validator(validator_value)
def __merge_parent_preset_dicts_if_present(self, config: ConfigFile):
parent_preset_validator = self._validate_key_if_present(

View file

@ -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:
"""

View file

@ -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 = {}

View file

@ -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")

View file

@ -1,8 +1,10 @@
import os
from abc import ABC
from collections import defaultdict
from pathlib import Path
from typing import Dict
from typing import Generic
from typing import List
from typing import Optional
from typing import Type
from typing import TypeVar
@ -95,22 +97,30 @@ class SharedNfoTagsPlugin(
Shared code between NFO tags and Ouptut Directory NFO Tags
"""
def _get_xml_element_dict(self, entry: Optional[Entry]) -> Dict[str, XmlElement]:
nfo_tags: Dict[str, XmlElement] = {}
def _get_xml_element_dict(self, entry: Optional[Entry]) -> Dict[str, List[XmlElement]]:
nfo_tags: Dict[str, List[XmlElement]] = defaultdict(list)
for key, string_tag in self.plugin_options.tags.string_tags.items():
nfo_tags[key] = XmlElement(
text=self.overrides.apply_formatter(formatter=string_tag, entry=entry),
attributes={},
for key, string_tags in self.plugin_options.tags.string_tags.items():
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_tag in self.plugin_options.tags.attribute_tags.items():
nfo_tags[key] = XmlElement(
text=self.overrides.apply_formatter(formatter=attribute_tag.tag, entry=entry),
attributes={
attr_name: self.overrides.apply_formatter(formatter=attr_formatter, entry=entry)
for attr_name, attr_formatter in attribute_tag.attributes.dict.items()
},
for key, attribute_tags in self.plugin_options.tags.attribute_tags.items():
nfo_tags[key].extend(
XmlElement(
text=self.overrides.apply_formatter(formatter=attribute_tag.tag, entry=entry),
attributes={
attr_name: self.overrides.apply_formatter(
formatter=attr_formatter, entry=entry
)
for attr_name, attr_formatter in attribute_tag.attributes.dict.items()
},
)
for attribute_tag in attribute_tags
)
return nfo_tags
@ -125,11 +135,14 @@ class SharedNfoTagsPlugin(
if self.plugin_options.kodi_safe:
nfo_root = to_max_3_byte_utf8_string(nfo_root)
nfo_tags = {
to_max_3_byte_utf8_string(key): XmlElement(
text=to_max_3_byte_utf8_string(xml_elem.text),
attributes=to_max_3_byte_utf8_dict(xml_elem.attributes),
)
for key, xml_elem in nfo_tags.items()
to_max_3_byte_utf8_string(key): [
XmlElement(
text=to_max_3_byte_utf8_string(xml_elem.text),
attributes=to_max_3_byte_utf8_dict(xml_elem.attributes),
)
for xml_elem in xml_elems
]
for key, xml_elems in nfo_tags.items()
}
xml = to_xml(nfo_dict=nfo_tags, nfo_root=nfo_root)
@ -147,9 +160,16 @@ class SharedNfoTagsPlugin(
# Save the nfo file and log its metadata
nfo_metadata = FileMetadata.from_dict(
value_dict={
nfo_root: {key: xml_elem.to_dict_value() for key, xml_elem in nfo_tags.items()}
nfo_root: {
key: (
xml_elems[0].to_dict_value()
if len(xml_elems) == 1
else [xml_elem.to_dict_value() for xml_elem in xml_elems]
)
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)
@ -210,7 +230,7 @@ class NfoTagsOptions(
<episode>502</episode>
</episodedetails>
Also supports xml attributes:
Also supports xml attributes and duplicate keys:
.. code-block:: yaml
@ -219,12 +239,17 @@ class NfoTagsOptions(
attributes:
name: "Best Year"
tag: "{upload_year}"
genre:
- "Comedy"
- "Drama"
Which translates to
.. code-block:: xml
<season name="Best Year">2022</season>
<genre>Comedy</genre>
<genre>Drama</genre>
"""
return self._tags

View file

@ -62,7 +62,7 @@ class OutputDirectoryNfoTagsOptions(
<title>Sweet youtube TV show</title>
</tvshow>
Also supports xml attributes:
Also supports xml attributes and duplicate keys:
.. code-block:: yaml
@ -71,12 +71,17 @@ class OutputDirectoryNfoTagsOptions(
attributes:
year: "2022"
tag: "Sweet youtube TV show"
genre:
- "Comedy"
- "Drama"
Which translates to
.. code-block:: xml
<title year="2022">Sweet youtube TV show</season>
<genre>Comedy</genre>
<genre>Drama</genre>
"""
return self._tags

View file

@ -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,
)

View file

@ -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")

View file

@ -1,3 +1,4 @@
import json
import os
import shutil
from pathlib import Path
@ -56,32 +57,60 @@ 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}
def _recursive_add_dict_lines(rdict: Dict, indent: int):
rdict_items = rdict.items()
if sort_dict:
rdict_items = sorted(rdict_items)
if sort_dict:
value_dict = json.loads(json.dumps(value_dict, sort_keys=True, ensure_ascii=False))
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()}")
def _indent_lines(value: str, indent: int) -> str:
if "\n" not in value:
return value
output_str = ""
_indent = " " * indent
for line in value.split("\n"):
output_str += f"{_indent}{line}\n"
return f"{output_str.rstrip()}\n"
def _single_value(value: Any) -> Optional[str]:
if isinstance(value, list) and len(value) == 1:
return _single_value(value=value[0])
if isinstance(value, (dict, list)):
return None
if isinstance(value, str) and "\n" in value:
return None
return value
def _recursive_lines(value: Any, indent: int = 0) -> str:
_indent = " " * indent
output_str = ""
if isinstance(value, dict):
for key, sub_value in value.items():
single_sub_value = _single_value(sub_value)
if single_sub_value is not None:
output_str += f"{_indent}{key}: {single_sub_value}\n"
else:
lines.append(f"{_indent}{key}: {value}")
output_str += f"{_indent}{key}:\n"
output_str += _indent_lines(_recursive_lines(sub_value), indent=indent + 2)
_recursive_add_dict_lines(rdict=value_dict, indent=2)
return cls(metadata=lines)
elif isinstance(value, list):
for sub_value in value:
single_sub_value = _single_value(sub_value)
if single_sub_value is not None:
output_str += f"{_indent}- {single_sub_value}\n"
else:
output_str += f"{_indent}- \n"
output_str += _indent_lines(_recursive_lines(sub_value), indent=indent + 2)
elif isinstance(value, str): # multi-line string
output_str += _indent_lines(value, indent=indent)
else:
assert False, "should never reach here"
return output_str
out = _recursive_lines(value_dict).rstrip().split("\n")
return cls(metadata=out)
class FileHandlerTransactionLog:

View file

@ -2,6 +2,7 @@ import xml.etree.ElementTree as et
from dataclasses import dataclass
from typing import Any
from typing import Dict
from typing import List
from typing import Union
@ -59,7 +60,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 +76,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)

View file

@ -1,15 +1,20 @@
from abc import ABC
from collections import defaultdict
from typing import Dict
from typing import Generic
from typing import List
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 ListFormatterValidator
from ytdl_sub.validators.string_formatter_validators import 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)
@ -53,50 +58,80 @@ 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
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
):
_tags_with_attributes_validator: Type[TNfoTagsWithAttributesValidator]
class SharedNfoTagsValidator(DictValidator, ABC):
_tags_validator: Type[TNfoTagsListValidator]
_tags_with_attributes_validator: Type[TNfoTagsWithAttributesListValidator]
def __init__(self, name, value):
super().__init__(name, value)
self._string_tags: Dict[str, StringFormatterValidator] = {}
self._attribute_tags: Dict[str, TNfoTagsWithAttributesValidator] = {}
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():
if isinstance(tag_value, str):
self._string_tags[key] = self._validate_key(
key=key, validator=self._tags_with_attributes_validator.formatter_validator
# Turn each value into a list if it's not
if not isinstance(tag_value, list):
tag_value = [tag_value]
if isinstance(tag_value[0], str):
self._string_tags[key].extend(
self._validate_key(
key=key,
validator=self._tags_validator,
).list
)
elif isinstance(tag_value, dict):
self._attribute_tags[key] = self._validate_key(
key=key, validator=self._tags_with_attributes_validator
elif isinstance(tag_value[0], dict):
self._attribute_tags[key].extend(
self._validate_key(key=key, validator=self._tags_with_attributes_validator).list
)
else:
raise self._validation_exception("must either be a string or attributes object")
raise self._validation_exception(
"must either be a single or list of string/attribute object"
)
@property
def string_tags(self) -> Dict[str, StringFormatterValidator]:
def string_tags(self) -> Dict[str, List[TStringFormatterValidator]]:
"""
Returns
-------
@ -105,7 +140,7 @@ class SharedNfoTagsValidator(
return self._string_tags
@property
def attribute_tags(self) -> Dict[str, TNfoTagsWithAttributesValidator]:
def attribute_tags(self) -> Dict[str, List[TNfoTagsWithAttributesValidator]]:
"""
Returns
-------
@ -114,11 +149,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

View file

@ -209,6 +209,10 @@ class ListFormatterValidator(ListValidator[StringFormatterValidator]):
_inner_list_type = StringFormatterValidator
class ListOverridesFormatterValidator(ListValidator[StringFormatterValidator]):
_inner_list_type = OverridesStringFormatterValidator
class DictFormatterValidator(LiteralDictValidator):
"""
A dict made up of

View file

@ -23,6 +23,17 @@ def subscription_dict(output_directory):
"attributes": {"🎸?": "value\nnewlines 🎸"},
"tag": "the \n tag 🎸🎸",
},
"kodi_safe_multi_title 🎸": ["value 1 🎸", "value 2 🎸"],
"kodi_safe_multi_title_with_attrs": [
{
"attributes": {"🎸?": "value\nnewlines 🎸"},
"tag": "the \n tag 1 🎸🎸",
},
{
"attributes": {"🎸?": "value\nnewlines 🎸"},
"tag": "the \n tag 2 🎸🎸",
},
],
},
},
"output_directory_nfo_tags": {
@ -34,6 +45,17 @@ def subscription_dict(output_directory):
"attributes": {"🎸?": "value\nnewlines 🎸"},
"tag": "the \n tag 🎸🎸",
},
"kodi_safe_multi_title 🎸": ["value 1 🎸", "value 2 🎸"],
"kodi_safe_multi_title_with_attrs": [
{
"attributes": {"🎸?": "value\nnewlines 🎸"},
"tag": "the \n tag 1 🎸🎸",
},
{
"attributes": {"🎸?": "value\nnewlines 🎸"},
"tag": "the \n tag 2 🎸🎸",
},
],
},
},
}
@ -74,3 +96,17 @@ class TestNfoTagsPlugins:
preset_name="kodi_safe_xml",
preset_dict=subscription_dict,
)
def test_source_variable_in_output_directory_nfo_tags_list_errors(
self, subscription_dict, music_video_config
):
subscription_dict["output_directory_nfo_tags"]["tags"]["kodi_safe_title_with_attrs"] = [
"okay value",
"not okay {title}",
]
with pytest.raises(ValidationException):
Subscription.from_dict(
config=music_video_config,
preset_name="kodi_safe_xml",
preset_dict=subscription_dict,
)

View file

@ -8,6 +8,26 @@ Rick Beato - Can you hear the difference 🎸🔥 #shorts.nfo
musicvideo:
album: Music Videos
artist: Rick Beato
kodi_safe_multi_title 🎸:
- value 1 🎸
- value 2 🎸
kodi_safe_multi_title_with_attrs:
-
attributes:
🎸?:
value
newlines 🎸
tag:
the
tag 1 🎸🎸
-
attributes:
🎸?:
value
newlines 🎸
tag:
the
tag 2 🎸🎸
kodi_safe_title 🎸: kodi_safe_value 🎸
kodi_safe_title_with_attrs:
attributes:
@ -16,12 +36,32 @@ Rick Beato - Can you hear the difference 🎸🔥 #shorts.nfo
newlines 🎸
tag:
the
tag 🎸🎸
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 🎸
kodi_safe_multi_title_with_attrs:
-
attributes:
🎸?:
value
newlines 🎸
tag:
the
tag 1 🎸🎸
-
attributes:
🎸?:
value
newlines 🎸
tag:
the
tag 2 🎸🎸
kodi_safe_title 🎸: kodi_safe_value 🎸
kodi_safe_title_with_attrs:
attributes:
@ -30,4 +70,4 @@ test.nfo
newlines 🎸
tag:
the
tag 🎸🎸
tag 🎸🎸

View file

@ -8,6 +8,26 @@ Rick Beato - Can you hear the difference 🎸🔥 #shorts.nfo
musicvideo:
album: Music Videos
artist: Rick Beato
kodi_safe_multi_title □:
- value 1 □
- value 2 □
kodi_safe_multi_title_with_attrs:
-
attributes:
□?:
value
newlines □
tag:
the
tag 1 □□
-
attributes:
□?:
value
newlines □
tag:
the
tag 2 □□
kodi_safe_title □: kodi_safe_value □
kodi_safe_title_with_attrs:
attributes:
@ -16,12 +36,32 @@ Rick Beato - Can you hear the difference 🎸🔥 #shorts.nfo
newlines □
tag:
the
tag □□
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 □
kodi_safe_multi_title_with_attrs:
-
attributes:
□?:
value
newlines □
tag:
the
tag 1 □□
-
attributes:
□?:
value
newlines □
tag:
the
tag 2 □□
kodi_safe_title □: kodi_safe_value □
kodi_safe_title_with_attrs:
attributes:
@ -30,4 +70,4 @@ test.nfo
newlines □
tag:
the
tag □□
tag □□

View file

@ -14,7 +14,7 @@ JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.mp4
Video Tags:
description:
🎸 / ' "
newline?
newline?
JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.nfo
NFO tags:
musicvideo:

View file

@ -3,7 +3,7 @@ Files created in '{output_directory}'
JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case-thumb.jpg
JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.info.json
JMC - This GPU SLIDES into this Case! - Silverstone SUGO 16 ITX Case.mp4
Embedded Chapters
Embedded Chapters:
Removed Chapter(s): Intro, Outro
Removed SponsorBlock Category Count(s):
Sponsor: 2

View file

@ -107,7 +107,8 @@ class TestPreset:
nfo_options: NfoTagsOptions = preset.plugins.get(NfoTagsOptions)
tags_string_dict = {
key: formatter.format_string for key, formatter in nfo_options.tags.string_tags.items()
key: formatter[0].format_string
for key, formatter in nfo_options.tags.string_tags.items()
}
assert tags_string_dict == {"key-1": "preset_0", "key-2": "this-preset"}
@ -126,7 +127,8 @@ class TestPreset:
nfo_options: NfoTagsOptions = preset.plugins.get(NfoTagsOptions)
tags_string_dict = {
key: formatter.format_string for key, formatter in nfo_options.tags.string_tags.items()
key: formatter[0].format_string
for key, formatter in nfo_options.tags.string_tags.items()
}
assert tags_string_dict == {