Skip to content

Commit 5017b42

Browse files
authored
Merge pull request #236 from cjcodeproj/integration
Integration to Main (v0.2.9)
2 parents 178d324 + 766252e commit 5017b42

File tree

10 files changed

+348
-27
lines changed

10 files changed

+348
-27
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
medialibrary CHANGELOG
22
======================
33

4+
## RELEASE 0.2.9 (Aug, 2025)
5+
6+
- [medialibrary-234](https://github.com/cjcodeproj/medialibrary/issues/234) Release 0.2.9
7+
- [medialibrary-232](https://github.com/cjcodeproj/medialibrary/issues/232) pylint error in audio/elements/internal.py
8+
- [medialibrary-223](https://github.com/cjcodeproj/medialibrary/issues/223) Generic keywords do not strip leading or trailing whitespace
9+
- [medialibrary-230](https://github.com/cjcodeproj/medialibrary/issues/230) Support variant data structure
10+
- [medialibrary-203](https://github.com/cjcodeproj/medialibrary/issues/203) Internal function definition has bad default value
11+
- [medialibrary-227](https://github.com/cjcodeproj/medialibrary/issues/227) Re-factor composer for new XML structure
12+
13+
414
## RELEASE 0.2.7
515

616
- [medialibrary-220](https://github.com/cjcodeproj/medialibrary/issues/220) Release 0.2.7

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "medialibrary"
3-
version = "0.2.7"
3+
version = "0.2.9"
44
description = "Package for reading vtmedia XML files describing a movie/music media library"
55
readme = "README.md"
66
requires-python = ">3.8"

src/media/data/media/contents/audio/elements/internal.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python
22

33
#
4-
# Copyright 2024 Chris Josephes
4+
# Copyright 2025 Chris Josephes
55
#
66
# Permission is hereby granted, free of charge, to any person obtaining a copy
77
# of this software and associated documentation files (the "Software"), to deal
@@ -27,7 +27,7 @@
2727
'''
2828

2929
# pylint: disable=too-few-public-methods
30-
# pylint: disable=R0801
30+
# pylint: disable=R0801, W0613
3131

3232
from media.data.media.contents.generic.catalog import AbstractCatalog
3333
from media.data.nouns import noun_dispatcher
@@ -56,8 +56,6 @@ def _extract_catalog_from(self, in_parent):
5656
self.catalog = ElementCatalog(None)
5757
if in_parent.catalog.artists is not None:
5858
self.catalog.artists = in_parent.catalog.artists
59-
if in_parent.catalog.composers is not None:
60-
self.catalog.composers = in_parent.catalog.composers
6159

6260

6361
class ElementTitle():
@@ -86,31 +84,23 @@ class ElementCatalog(AbstractCatalog):
8684
def __init__(self, in_element):
8785
super().__init__()
8886
self.artists = []
89-
self.composers = []
90-
if in_element is not None:
91-
self._process(in_element)
87+
# We can't call process here, because it'll
88+
# it would run prematurely before subclass
89+
# __init__ completes.
9290

9391
def _process(self, in_element):
9492
super()._process(in_element)
9593
for child in in_element:
9694
e_name = Namespaces.ns_strip(child.tag)
9795
if e_name == 'artists':
9896
self._process_artists(child)
99-
elif e_name == 'composers':
100-
self._process_composers(child)
10197

10298
def _process_artists(self, in_element):
10399
for child in in_element:
104100
e_name = Namespaces.ns_strip(child.tag)
105101
if e_name == 'artist':
106102
self.artists.append(noun_dispatcher(child))
107103

108-
def _process_composers(self, in_element):
109-
for child in in_element:
110-
e_name = Namespaces.ns_strip(child.tag)
111-
if e_name == 'composer':
112-
self.composers.append(noun_dispatcher(child))
113-
114104

115105
class ElementTechnical():
116106
'''

src/media/data/media/contents/audio/elements/song.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@
3131

3232
from media.data.media.contents.genericv.technical import process_iso_duration
3333
from media.data.media.contents.audio.elements import (
34-
AbstractElement, ElementTechnical
34+
AbstractElement, ElementCatalog, ElementTechnical
3535
)
36+
from media.data.nouns import noun_dispatcher
3637
from media.xml.namespaces import Namespaces
3738

3839

@@ -54,12 +55,64 @@ def _process(self, in_element=None):
5455
e_name = Namespaces.ns_strip(child.tag)
5556
if e_name == 'technical':
5657
self.technical = SongTechnical(child)
58+
elif e_name == 'catalog':
59+
self.catalog = SongCatalog(child)
5760
self._post_load_process()
5861

5962
def _post_load_process(self):
6063
if not self.catalog:
6164
self._extract_catalog_from(self.parent_o)
6265

66+
def _extract_catalog_from(self, in_parent):
67+
super()._extract_catalog_from(in_parent)
68+
if in_parent.catalog is not None:
69+
if in_parent.catalog.composers is not None:
70+
self.catalog.composers = in_parent.catalog.composers
71+
72+
73+
class SongCatalog(ElementCatalog):
74+
'''
75+
Catalog specific to a song.
76+
'''
77+
def __init__(self, in_element):
78+
super().__init__(in_element)
79+
self.composers = []
80+
if in_element is not None:
81+
self._process(in_element)
82+
83+
def _process(self, in_element):
84+
super()._process(in_element)
85+
for child in in_element:
86+
e_name = Namespaces.ns_strip(child.tag)
87+
if e_name == 'composers':
88+
self._process_composers(child)
89+
90+
def _process_composers(self, in_element):
91+
for child in in_element:
92+
self.composers.append(SongComposer(child))
93+
94+
95+
class SongComposer():
96+
'''
97+
Object representing a composer.
98+
'''
99+
def __init__(self, in_element):
100+
self.name = None
101+
self.publishers = []
102+
self.rights = None
103+
if in_element is not None:
104+
self._process(in_element)
105+
106+
def _process(self, in_element):
107+
for child in in_element:
108+
e_name = Namespaces.ns_strip(child.tag)
109+
if e_name == 'name':
110+
self.name = noun_dispatcher(child)
111+
elif e_name == 'publisher':
112+
self.publishers.append(child.text)
113+
elif e_name == 'rights':
114+
self.rights = child.text
115+
63116

64117
class SongTechnical(ElementTechnical):
65118
'''

src/media/data/media/contents/generic/keywords.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python
22

33
#
4-
# Copyright 2024 Chris Josephes
4+
# Copyright 2025 Chris Josephes
55
#
66
# Permission is hereby granted, free of charge, to any person obtaining a copy
77
# of this software and associated documentation files (the "Software"), to deal
@@ -203,7 +203,7 @@ class GenericKeyword(AbstractKeyword):
203203
'''
204204
def __init__(self, in_element):
205205
super().__init__(in_element)
206-
self.value = in_element.text
206+
self.value = in_element.text.strip()
207207
self.sort_value = self.value.casefold()
208208
self.type = 'generic'
209209

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#!/usr/bin/env python
2+
3+
#
4+
# Copyright 2025 Chris Josephes
5+
#
6+
# Permission is hereby granted, free of charge, to any person obtaining a copy
7+
# of this software and associated documentation files (the "Software"), to deal
8+
# in the Software without restriction, including without limitation the rights
9+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
# copies of the Software, and to permit persons to whom the Software is
11+
# furnished to do so, subject to the following conditions:
12+
#
13+
# The above copyright notice and this permission notice shall be included in
14+
# all copies or substantial portions of the Software.
15+
#
16+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
# SOFTWARE.
23+
#
24+
25+
'''
26+
Code to handle technical aspects of a vidsual format media
27+
(movies, television)
28+
'''
29+
30+
# pylint: disable=too-few-public-methods
31+
32+
from media.xml.namespaces import Namespaces
33+
from media.data.media.contents.genericv.technical import Technical
34+
35+
36+
class VariantPool():
37+
'''
38+
A class to handle variant loading.
39+
'''
40+
@classmethod
41+
def read_variants(cls, in_element):
42+
'''
43+
Read a variants element structure and create
44+
the variant child objects.
45+
'''
46+
variants = []
47+
if in_element is not None:
48+
for child in in_element:
49+
tagname = Namespaces.ns_strip(child.tag)
50+
if tagname == 'original':
51+
variants.append(OriginalVariant(child))
52+
elif tagname == 'variant':
53+
variants.append(Variant(child))
54+
return variants
55+
56+
57+
class AbstractVariant():
58+
'''
59+
Abstract Variant class.
60+
'''
61+
def __init__(self):
62+
self.id = ''
63+
self.name = ''
64+
self.notes = ''
65+
66+
def _process_xml_stream(self, in_element):
67+
if 'id' in in_element.attrib:
68+
self.id = in_element.attrib['id']
69+
for child in in_element:
70+
tagname = Namespaces.ns_strip(child.tag)
71+
if tagname == 'name':
72+
self.name = child.text
73+
elif tagname == 'notes':
74+
self.notes = child.text
75+
76+
77+
class OriginalVariant(AbstractVariant):
78+
'''
79+
Original type Variant class
80+
'''
81+
def __init__(self, in_element):
82+
super().__init__()
83+
if in_element is not None:
84+
self._process_xml_stream(in_element)
85+
86+
87+
class Variant(AbstractVariant):
88+
'''
89+
Regular type Variant class.
90+
'''
91+
92+
def __init__(self, in_element):
93+
super().__init__()
94+
self.techincal = None
95+
if in_element is not None:
96+
self._process_xml_stream(in_element)
97+
98+
def _process_xml_stream(self, in_element):
99+
super()._process_xml_stream(in_element)
100+
for child in in_element:
101+
tagname = Namespaces.ns_strip(child.tag)
102+
if tagname == 'technical':
103+
self.technical = Technical(child)

src/media/data/media/contents/movie/internal.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from media.data.media.contents.generic.story import Story
3535
from media.data.media.contents.genericv.crew import Crew
3636
from media.data.media.contents.genericv.technical import Technical
37+
from media.data.media.contents.genericv.variants import VariantPool
3738
from media.data.media.contents.movie.classification import Classification
3839
from media.general.sorting.index import ContentIndex
3940

@@ -43,6 +44,7 @@ class Movie(AbstractContent):
4344
def __init__(self, in_element):
4445
super().__init__()
4546
self.technical = None
47+
self.variants = []
4648
self.crew = None
4749
self.s_index = None
4850
self._process(in_element)
@@ -59,13 +61,15 @@ def _process(self, in_element):
5961
for child in in_element:
6062
if child.tag == Namespaces.nsf('movie') + 'classification':
6163
self.classification = Classification(child)
62-
if child.tag == Namespaces.nsf('movie') + 'technical':
64+
elif child.tag == Namespaces.nsf('movie') + 'technical':
6365
self.technical = Technical(child)
64-
if child.tag == Namespaces.nsf('movie') + 'story':
66+
elif child.tag == Namespaces.nsf('movie') + 'story':
6567
self.story = Story(child)
66-
if child.tag == Namespaces.nsf('movie') + 'description':
68+
elif child.tag == Namespaces.nsf('movie') + 'description':
6769
self.story = Story(child)
68-
if child.tag == Namespaces.nsf('movie') + 'crew':
70+
elif child.tag == Namespaces.nsf('movie') + 'variants':
71+
self.variants = VariantPool.read_variants(child)
72+
elif child.tag == Namespaces.nsf('movie') + 'crew':
6973
self.crew = Crew(child)
7074
self._post_load_process()
7175

src/media/tools/movies/common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ def _stats(self):
200200
out += f" {'Sample count':12s} : {wrk_c:5d} ({wrk_p:5.2f}%)\n"
201201
return out
202202

203-
def _out_batch(self, batch, sort_field=1):
203+
def _out_batch(self, batch, sort_field=Batch.S_TITLE):
204204
'''
205205
Generate the output for a single batch.
206206
'''

test/media/data/media/contents/audio/elements/test_song.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@
5050
<artist><gn>Blake</gn><fn>Drake</fn></artist>
5151
</artists>
5252
<composers>
53-
<composer><gn>Chad</gn><fn>Brooks</fn></composer>
53+
<composer>
54+
<name><gn>Chad</gn><fn>Brooks</fn></name>
55+
<publisher>Drunken Tirade Music, LLC</publisher>
56+
</composer>
5457
</composers>
5558
</catalog>
5659
<technical>
@@ -133,14 +136,14 @@ def test_song_catalog_first_composer_obj(self):
133136
Verify object of first composer passed.
134137
'''
135138
song_comp_one = self.album.elements[0].catalog.composers[0]
136-
self.assertIsInstance(song_comp_one, PersonalName)
139+
self.assertIsInstance(song_comp_one.name, PersonalName)
137140

138141
def test_song_catalog_first_composer_val(self):
139142
'''
140143
Verify value of first composer passed.
141144
'''
142145
song_comp_one = self.album.elements[0].catalog.composers[0]
143-
self.assertEqual(str(song_comp_one), 'Chad Brooks')
146+
self.assertEqual(str(song_comp_one.name), 'Chad Brooks')
144147

145148

146149
class TestAlbumElementInheritedCatalog(unittest.TestCase):

0 commit comments

Comments
 (0)