Hide keyboard shortcuts

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> 

3 

4This file is part of toktokkie. 

5 

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. 

10 

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. 

15 

16You should have received a copy of the GNU General Public License 

17along with toktokkie. If not, see <http://www.gnu.org/licenses/>. 

18LICENSE""" 

19 

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 

29 

30 

31class MusicSong: 

32 """ 

33 Class that models a single song 

34 """ 

35 

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

42 

43 self.logger = logging.getLogger(self.__class__.__name__) 

44 

45 self.album = album 

46 self.path = path 

47 self.filename = str(os.path.basename(self.path)) 

48 

49 self.format = get_ext(self.path) 

50 

51 tags = {} # type: Dict[str, str] 

52 if self.format == "mp3": 

53 tags = self.__load_mp3_tags() 

54 self._tags = tags 

55 

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

66 

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

74 

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 

83 

84 except ID3NoHeaderError: 

85 return {} 

86 

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() 

100 

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] 

114 

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 

122 

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) 

129 

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 

137 

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) 

144 

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 

152 

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) 

159 

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 

167 

168 @property 

169 def genre(self) -> str: 

170 """ 

171 :return: The song's genre 

172 """ 

173 return self._tags.get("genre", self.album.genre) 

174 

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 

182 

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

192 

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 

204 

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) 

215 

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

222 

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)