Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""LICENSE
2Copyright 2015 Hermann Krumrey <hermann@krumreyh.com>
4This file is part of toktokkie.
6toktokkie is free software: you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation, either version 3 of the License, or
9(at your option) any later version.
11toktokkie is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
16You should have received a copy of the GNU General Public License
17along with toktokkie. If not, see <http://www.gnu.org/licenses/>.
18LICENSE"""
20import os
21import logging
22from typing import Dict, Tuple, TYPE_CHECKING
23from mutagen.easyid3 import EasyID3
24# noinspection PyProtectedMember
25from mutagen.id3._util import ID3NoHeaderError
26from puffotter.os import get_ext
27if TYPE_CHECKING: # pragma: no cover
28 from toktokkie.metadata.music.components.MusicAlbum import MusicAlbum
31class MusicSong:
32 """
33 Class that models a single song
34 """
36 def __init__(self, path: str, album: "MusicAlbum"):
37 """
38 Initializes the object
39 :param path: The path to the song file
40 :param album: The album this song is a part of
41 """
43 self.logger = logging.getLogger(self.__class__.__name__)
45 self.album = album
46 self.path = path
47 self.filename = str(os.path.basename(self.path))
49 self.format = get_ext(self.path)
51 tags = {} # type: Dict[str, str]
52 if self.format == "mp3":
53 tags = self.__load_mp3_tags()
54 self._tags = tags
56 def save_tags(self):
57 """
58 Saves any edited tags
59 :return: None
60 """
61 if self.format == "mp3":
62 self.__write_mp3_tags()
63 else:
64 self.logger.warning("Can't write tags for {}: "
65 "not a known file type".format(self.path))
67 def __load_mp3_tags(self) -> Dict[str, str]:
68 """
69 Loads MP3 tags if this is an mp3 file
70 :return: The mp3 tags as a dictionary
71 """
72 try:
73 tags = dict(EasyID3(self.path))
75 for key in tags:
76 tag = tags[key]
77 if isinstance(tag, list):
78 if len(tag) >= 1:
79 tags[key] = tag[0]
80 else:
81 tags[key] = ""
82 return tags
84 except ID3NoHeaderError:
85 return {}
87 def __write_mp3_tags(self):
88 """
89 Writes the current tags to the file as ID3 tags, if this is an mp3 file
90 :return: None
91 """
92 mp3 = EasyID3(self.path)
93 for key, tag in self._tags.items():
94 if tag == "":
95 if key in mp3:
96 mp3.pop(key)
97 else:
98 mp3[key] = tag
99 mp3.save()
101 @property
102 def title(self) -> str:
103 """
104 :return: The title of the song
105 """
106 title = self._tags.get("title")
107 if title is not None:
108 return title
109 else:
110 if self.filename.split(" - ")[0].isnumeric():
111 return self.filename.split(" - ", 1)[1].rsplit(".", 1)[0]
112 else:
113 return self.filename.rsplit(".", 1)[0]
115 @title.setter
116 def title(self, title: str):
117 """
118 :param title: The title of the song
119 :return: None
120 """
121 self._tags["title"] = title
123 @property
124 def artist_name(self) -> str:
125 """
126 :return: The song's artist name
127 """
128 return self._tags.get("artist", self.album.artist_name)
130 @artist_name.setter
131 def artist_name(self, name: str):
132 """
133 :param name: The song's artist name
134 :return: None
135 """
136 self._tags["artist"] = name
138 @property
139 def album_artist_name(self) -> str:
140 """
141 :return: The song's album artist name
142 """
143 return self._tags.get("albumartist", self.album.artist_name)
145 @album_artist_name.setter
146 def album_artist_name(self, name: str):
147 """
148 :param name: The song's album artist name
149 :return: None
150 """
151 self._tags["albumartist"] = name
153 @property
154 def album_name(self) -> str:
155 """
156 :return: The song's album name
157 """
158 return self._tags.get("album", self.album.name)
160 @album_name.setter
161 def album_name(self, name: str):
162 """
163 :param name: The song's album name
164 :return: None
165 """
166 self._tags["album"] = name
168 @property
169 def genre(self) -> str:
170 """
171 :return: The song's genre
172 """
173 return self._tags.get("genre", self.album.genre)
175 @genre.setter
176 def genre(self, genre: str):
177 """
178 :param genre: The song's genre
179 :return: None
180 """
181 self._tags["genre"] = genre
183 @property
184 def tracknumber(self) -> Tuple[int, int]:
185 """
186 :return: The song's track number as a tuple consisting of the song's
187 track number and the total amount of tracks in the album
188 """
189 tracks = [x.path for x in self.album.load_songs(False)]
190 track_count = len(tracks)
191 tracknumber = self._tags.get("tracknumber")
193 if tracknumber is not None:
194 split = tracknumber.split("/")
195 if len(split) == 1:
196 return int(tracknumber), track_count
197 else:
198 return int(split[0]), int(split[1])
199 else:
200 for i, track_path in enumerate(tracks):
201 if track_path == self.path:
202 return i + 1, track_count
203 return 1, track_count
205 @tracknumber.setter
206 def tracknumber(self, track_number: Tuple[int, int]):
207 """
208 :param track_number: The song's track number as a tuple consisting of
209 the song's track number and the total amount
210 of tracks in the album
211 :return: None
212 """
213 track, total = track_number
214 self._tags["tracknumber"] = "{}/{}".format(track, total)
216 @property
217 def year(self) -> int:
218 """
219 :return: The year this song was released
220 """
221 return int(self._tags.get("date", self.album.year))
223 @year.setter
224 def year(self, year: int):
225 """
226 :param year: The year this song was released
227 :return: None
228 """
229 self._tags["date"] = str(year)