-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #35 from nv95/feature/ogg
Add Ogg/Vorbis support
- Loading branch information
Showing
5 changed files
with
219 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import base64 | ||
import mutagen.flac | ||
from typing import Dict | ||
from mutagen.flac import Picture | ||
from mutagen.id3 import ID3, TIT2, APIC, TALB, TPE1 # noqa:F401 | ||
from mutagen.id3 import TCON, TRCK, TDRC, USLT # noqa:F401 | ||
from mutagen.oggvorbis import OggVorbis, OggVCommentDict | ||
|
||
from .audio_extension_handler import AudioExtensionHandler | ||
from .tools import file_size_to_string | ||
from .tools import music_length_to_string | ||
|
||
TAG_PARAMS = { | ||
"title": "TITLE", | ||
"cover": "METADATA_BLOCK_PICTURE", | ||
"album": "ALBUM", | ||
"artist": "ARTIST", | ||
"genre": "GENRE", | ||
"track": "TRACKNUMBER", | ||
"year": "DATE", | ||
} | ||
|
||
|
||
class OggFileHandler(AudioExtensionHandler): | ||
|
||
@staticmethod | ||
def get_extension(): | ||
return ".ogg" | ||
|
||
def __init__(self, path_file): | ||
""" | ||
We initialise the path of the file and the tagging tool we use | ||
""" | ||
self.path_file = path_file | ||
self.audio = OggVorbis(path_file) | ||
self.id3 = self.audio.tags | ||
|
||
if self.id3 is None: | ||
self.audio.add_tags() | ||
self.id3 = self.audio.tags | ||
|
||
def get_one_tag(self, id3_name_tag: str, data_type: str): | ||
""" | ||
A function to return the first tag of an id3 label | ||
""" | ||
if self.id3 is None: | ||
return "" | ||
|
||
if self.id3 is OggVCommentDict: | ||
return self.id3.get(id3_name_tag) | ||
|
||
tag_needed = self.id3.get(id3_name_tag, "") | ||
|
||
if len(tag_needed) == 0: | ||
return "" | ||
|
||
if data_type == "text": | ||
return tag_needed[0] | ||
elif data_type == "data": | ||
try: | ||
return base64.b64decode(tag_needed[0]) | ||
except (TypeError, ValueError): | ||
return "" | ||
else: | ||
return "" | ||
|
||
def get_tag_research(self): | ||
return [ | ||
self.get_one_tag(TAG_PARAMS["title"], "text"), | ||
self.get_one_tag(TAG_PARAMS["artist"], "text"), | ||
self.get_one_tag(TAG_PARAMS["album"], "text"), | ||
] | ||
|
||
def get_tag(self, tag_key): | ||
""" | ||
We handle tag using a switch, it is working well because it | ||
is basically the structure. | ||
""" | ||
|
||
if tag_key == "cover": | ||
cover_bytes = self.get_one_tag("METADATA_BLOCK_PICTURE", "data") | ||
if cover_bytes == "": | ||
return "" | ||
try: | ||
picture = Picture(cover_bytes) | ||
return picture.data | ||
except mutagen.flac.error: | ||
return "" | ||
elif tag_key == "size": | ||
return file_size_to_string(self.path_file) | ||
elif tag_key == "length": | ||
return music_length_to_string(self.audio.info.length) | ||
else: | ||
return self.get_one_tag(TAG_PARAMS[tag_key], "text") | ||
|
||
def get_tags(self) -> Dict: | ||
tags = [ | ||
"title", | ||
"album", | ||
"artist", | ||
"genre", | ||
"cover", | ||
"year", | ||
"track", | ||
"length", | ||
"size", | ||
] | ||
result = {} | ||
for tag in tags: | ||
result[tag] = self.get_tag(tag) | ||
return result | ||
|
||
def check_tag_existence(self, key): | ||
return len(self.id3.get(TAG_PARAMS[key])) > 0 | ||
|
||
def set_tag(self, tag_key, tag_value): | ||
|
||
if tag_key != "cover": | ||
self.id3[TAG_PARAMS[tag_key]] = [tag_value] | ||
return 1 | ||
|
||
if tag_value == "": | ||
return 0 | ||
|
||
if isinstance(tag_value, str): | ||
tag_value = open(tag_value, "rb").read() | ||
picture = Picture() | ||
picture.data = tag_value | ||
binary_data = picture.write() | ||
self.id3[TAG_PARAMS[tag_key]] = [base64.b64encode(binary_data).decode("ascii")] | ||
|
||
def save_modifications(self): | ||
""" | ||
Save definitively the modification we have made. | ||
""" | ||
self.audio.save() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
HANDLED_EXTENSIONS = ["mp3"] | ||
HANDLED_EXTENSIONS = ["mp3", "ogg"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
from unittest.mock import patch, Mock, call | ||
|
||
from src.audio_ogg_file_handler import OggFileHandler | ||
|
||
TESTED_MODULE = "src.audio_ogg_file_handler" | ||
|
||
|
||
def test_get_extension__return_ogg(): | ||
# given | ||
|
||
# when | ||
result = OggFileHandler.get_extension() | ||
|
||
# then | ||
assert result == ".ogg" | ||
|
||
|
||
@patch(f"{TESTED_MODULE}.OGG") | ||
def test_get_one_tag__return_empty_string_if_no_id3(m_ogg): | ||
# given | ||
oggfilehandler = OggFileHandler("fake_path.ogg") | ||
oggfilehandler.id3 = Mock() | ||
oggfilehandler.id3.get.return_value = [] | ||
|
||
# when | ||
result = oggfilehandler.get_one_tag("title", "text") | ||
|
||
# then | ||
assert result == "" | ||
|
||
|
||
@patch(f"{TESTED_MODULE}.MP3") | ||
def test_get_one_tag__return_tag_if_id3_with_text(m_ogg): | ||
# given | ||
oggfilehandler = OggFileHandler("fake_path.ogg") | ||
oggfilehandler.id3 = Mock() | ||
fake_tag = Mock() | ||
fake_tag.text = ["title"] | ||
oggfilehandler.id3.get.return_value = [fake_tag] | ||
|
||
# when | ||
result = oggfilehandler.get_one_tag("title", "text") | ||
|
||
# then | ||
assert result == "title" | ||
|
||
|
||
@patch(f"{TESTED_MODULE}.MP3") | ||
def test_get_one_tag__return_tag_if_id3_with_data(m_ogg): | ||
# given | ||
oggfilehandler = OggFileHandler("fake_path.ogg") | ||
oggfilehandler.id3 = Mock() | ||
fake_tag = Mock() | ||
fake_tag.data = ["data"] | ||
oggfilehandler.id3.get.return_value = [fake_tag] | ||
|
||
# when | ||
result = oggfilehandler.get_one_tag("title", "data") | ||
|
||
# then | ||
assert result == ["data"] | ||
|
||
|
||
@patch(f"{TESTED_MODULE}.MP3") | ||
@patch(f"{TESTED_MODULE}.OgFileHandler.get_one_tag") | ||
def test_get_tag_research__return_title_artist_and_album(m_get, m_ogg): | ||
# given | ||
oggfilehandler = OggFileHandler("fake_path.ogg") | ||
oggfilehandler.id3 = Mock() | ||
calls = [call("TITLE", "text"), call("ARTIST", "text"), call("ALBUM", "text")] | ||
|
||
# when | ||
oggfilehandler.get_tag_research() | ||
|
||
# then | ||
m_get.assert_has_calls(calls=calls, any_order=True) |