Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Metadata for downloaded audio - milestone #3
  • Loading branch information
Javinator9889 committed Oct 7, 2019
1 parent 08aaff2 commit a4da41a
Show file tree
Hide file tree
Showing 20 changed files with 174 additions and 23 deletions.
Empty file modified .gitignore 100644 → 100755
Empty file.
Empty file modified Design/Database/database_model.mwb 100644 → 100755
Empty file.
Empty file modified Design/Database/database_model.mwb.bak 100644 → 100755
Empty file.
Empty file modified Design/Database/generated_sql_file.sql 100644 → 100755
Empty file.
Empty file modified Design/Database/img_database_model.png 100644 → 100755
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified Design/Database/pdf_database_model.pdf 100644 → 100755
Empty file.
Empty file modified Design/Database/vect_database_model.svg 100644 → 100755
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified Design/SRS/Software Requirement Specification.docx 100644 → 100755
Empty file.
Empty file modified Design/SRS/Software Requirement Specification.pdf 100644 → 100755
Empty file.
Empty file modified Design/logo/logo.png 100644 → 100755
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified Design/logo/logo.psd 100644 → 100755
Empty file.
Empty file modified LICENSE 100644 → 100755
Empty file.
Empty file modified README.md 100644 → 100755
Empty file.
6 changes: 3 additions & 3 deletions YouTubeMDBot/audio/ffmpeg.py 100644 → 100755
Expand Up @@ -32,14 +32,14 @@ def ffmpeg_available() -> bool:

class FFmpegOpener(object):
def __init__(self, data: bytes):
io = BytesIO(data)
self._data = data
self.__ffmpeg_proc = Popen(["ffmpeg", "-i", "-", "-f", "s16le", "-"],
stdout=PIPE, stderr=PIPE, stdin=io)
stdout=PIPE, stderr=PIPE, stdin=PIPE)
self.__out = None
self.__err = None

def open(self) -> int:
self.__out, self.__err = self.__ffmpeg_proc.communicate()
self.__out, self.__err = self.__ffmpeg_proc.communicate(self._data)
return self.__ffmpeg_proc.returncode

def get_output(self) -> bytes:
Expand Down
45 changes: 45 additions & 0 deletions YouTubeMDBot/metadata/AudioMetadata.py
@@ -0,0 +1,45 @@
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from mutagen.mp4 import MP4
from mutagen.mp4 import MP4Cover
from io import BytesIO


class AudioMetadata:
def __init__(self, audio: BytesIO):
self._audio = MP4(audio)
self._data = audio

def set_title(self, title: str):
self._audio[u"\xa9nam"] = title

def set_artist(self, artist: str):
self._audio[u"\xa9ART"] = artist

def set_album(self, album: str):
self._audio[u"\xa9alb"] = album

def set_extras(self, extras: list):
self._audio[u"\xa9cmt"] = '; '.join(map(str, extras))

def set_cover(self, cover: bytes):
mp4_cover = MP4Cover(cover, MP4Cover.FORMAT_JPEG)
self._audio[u"covr"] = [mp4_cover]

def save(self) -> BytesIO:
self._data.seek(0)
self._audio.save(self._data)
return self._data
22 changes: 7 additions & 15 deletions YouTubeMDBot/metadata/MetadataIdentifier.py 100644 → 100755
Expand Up @@ -38,10 +38,10 @@ def __init__(self, audio: bytes):
self.recording_id: str = ""
self.score: float = 0.0
self.cover: bytes = bytes(0)
self.album: str = ""
self.duration: int = 0
self.youtube_data: bool = False
self.youtube_id: str = ""
# self._downloader = downloader

@staticmethod
def _is_valid_result(data: json) -> bool:
Expand All @@ -62,7 +62,7 @@ def identify_audio(self) -> bool:
data: json = acoustid.lookup(apikey=ACOUSTID_KEY,
fingerprint=fingerprint.fingerprint(),
duration=fingerprint.duration(),
meta="recordings releaseids")
meta="recordings releaseids releasegroups")
self.result = data
is_valid = self._is_valid_result(data)
if is_valid:
Expand All @@ -77,24 +77,16 @@ def identify_audio(self) -> bool:
else:
self.artist = "Unknown"
self.title = recording["title"]
self.release_id = recording["releases"][0]["id"]
if recording.get("releasegroups"):
self.release_id = \
recording["releasegroups"][0]["releases"][0]["id"]
self.album = recording["releasegroups"][0]["title"]
self.cover = musicbrainzngs.get_image_front(self.release_id)
self.recording_id = recording["id"]
self.duration = recording["duration"]
self.cover = musicbrainzngs.get_image_front(self.release_id)
is_valid = True
break
break
# elif self._downloader:
# from urllib.request import urlopen
#
# video_id = youtube_utils.get_yt_video_id(self._downloader.get_url())
# video_data = YouTubeAPI.video_details(video_id)
# self.title = video_data.title
# self.artist = video_data.artist
# self.duration = video_data.duration
# self.cover = urlopen(video_data.thumbnail).read()
# self.youtube_id = video_data.id
# self.youtube_data = True
return is_valid


Expand Down
2 changes: 2 additions & 0 deletions YouTubeMDBot/metadata/__init__.py 100644 → 100755
Expand Up @@ -15,3 +15,5 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ..metadata.MetadataIdentifier import MetadataIdentifier
from ..metadata.MetadataIdentifier import YouTubeMetadataIdentifier

from ..metadata.AudioMetadata import AudioMetadata
22 changes: 17 additions & 5 deletions YouTubeMDBot/tests/identifier.py 100644 → 100755
Expand Up @@ -3,6 +3,8 @@
from pprint import pprint
from time import sleep
from time import time
from typing import Tuple
from io import BytesIO

from YouTubeMDBot.downloader import YouTubeDownloader
from YouTubeMDBot.metadata import YouTubeMetadataIdentifier
Expand Down Expand Up @@ -38,23 +40,30 @@ def test_multiple_download_identification(self):
yt2 = YouTubeDownloader(url="https://www.youtube.com/watch?v=-_ZwpOdXXcA")
yt3 = YouTubeDownloader(url="https://www.youtube.com/watch?v=WOGWZD5iT10")
yt4 = YouTubeDownloader(url="https://www.youtube.com/watch?v=GfKV9KaNJXc")
yt5 = YouTubeDownloader(url="https://www.youtube.com/watch?v=DiItGE3eAyQ")
yt6 = YouTubeDownloader(url="https://www.youtube.com/watch?v=GuZzuQvv7uc")

t1 = threading.Thread(target=self.find_metadata, args=(yt1,))
t2 = threading.Thread(target=self.find_metadata, args=(yt2,))
t3 = threading.Thread(target=self.find_metadata, args=(yt3,))
t4 = threading.Thread(target=self.find_metadata, args=(yt4,))
t5 = threading.Thread(target=self.find_metadata, args=(yt5,))
t6 = threading.Thread(target=self.find_metadata, args=(yt6,))

self.max = 4
self.max = 6

t1.start()
t2.start()
t3.start()
t4.start()
t5.start()
t6.start()

while self.threads < self.max:
sleep(1)

pprint(self.song_info)
# pprint(self.song_info)
pprint("Finished")

def barrier(self):
with self.lock:
Expand All @@ -64,9 +73,9 @@ def getThreads(self):
with self.lock:
return self.threads

def find_metadata(self, downloader: YouTubeDownloader):
def find_metadata(self, downloader: YouTubeDownloader) -> Tuple[BytesIO, bytes]:
st_dl_t = time()
_, data = downloader.download()
io, data = downloader.download()
f_dl_t = time()
print("Downloaded {} - elapsed time: {:.1f}s".format(downloader.get_url(),
f_dl_t - st_dl_t))
Expand All @@ -75,19 +84,22 @@ def find_metadata(self, downloader: YouTubeDownloader):
assert valid
self.song_info[downloader.get_url()] = {
"title": identifier.title,
"artist": identifier.artist
"artist": identifier.artist,
"cover": identifier.cover
}
if not identifier.youtube_data:
self.song_info[downloader.get_url()]["score"] = identifier.score
self.song_info[downloader.get_url()]["record_id"] = \
"https://musicbrainz.org/recording/{0}".format(identifier.recording_id)
self.song_info[downloader.get_url()]["release_id"] = \
"https://musicbrainz.org/release/{0}".format(identifier.release_id)
self.song_info[downloader.get_url()]["album"] = identifier.album
else:
self.song_info[downloader.get_url()]["duration"] = identifier.duration
self.song_info[downloader.get_url()]["id"] = identifier.youtube_id
self.song_info[downloader.get_url()]["youtube_data"] = True
self.barrier()
return io, data


if __name__ == '__main__':
Expand Down
44 changes: 44 additions & 0 deletions YouTubeMDBot/tests/tagger.py
@@ -0,0 +1,44 @@
import unittest
import mutagen

from typing import Tuple
from io import BytesIO

from YouTubeMDBot.tests.identifier import IdentifierTest
from YouTubeMDBot.downloader import YouTubeDownloader
from YouTubeMDBot.metadata import AudioMetadata
from YouTubeMDBot.utils import youtube_utils


class TaggerTest(IdentifierTest):
def find_metadata(self, downloader: YouTubeDownloader) -> Tuple[BytesIO, bytes]:
io, data = super().find_metadata(downloader)
tagger = AudioMetadata(io)
url = downloader.get_url()

tagger.set_title(super().song_info[url]["title"])
tagger.set_artist(super().song_info[url]["artist"])
tagger.set_cover(super().song_info[url]["cover"])
extra = ["YouTube URL: " + url]
if not super().song_info[url].get("youtube_data"):
tagger.set_album(super().song_info[url]["album"])
extra.append("MusicBrainz Record ID: " + super().song_info[url][
"record_id"])
extra.append("MusicBrainz Release ID: " + super().song_info[url][
"release_id"])
tagger.set_extras(extra)
else:
tagger.set_extras(["YouTube ID: {}".format(super().song_info[url]["id"])])
yid = youtube_utils.get_yt_video_id(url)
rs = tagger.save()
rs.seek(0)
print(mutagen.File(rs).pprint())
rs.seek(0)
with open(yid + ".m4a", "wb") as f:
f.write(rs.read())
rs.seek(0)
return rs, rs.read()


if __name__ == '__main__':
unittest.main()
56 changes: 56 additions & 0 deletions YouTubeMDBot/utils/timeout.py
@@ -0,0 +1,56 @@
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import signal


class timeout:
def __init__(self, secs: int):
self.__secs = secs

def _raise_timeout(self, signum, frame):
raise TimeoutError("Function timeout! - {0}s".format(self.__secs))

def __enter__(self):
if self.__secs <= 0:
self._raise_timeout(0, 0)

signal.signal(signal.SIGALRM, self._raise_timeout)
signal.alarm(self.__secs)
yield

def __exit__(self, exc_type, exc_val, exc_tb):
signal.signal(signal.SIGALRM, signal.SIG_IGN)
return exc_val is not None


'''@contextmanager
def timeout(secs: int):
def raise_timeout(signum=None, frame=None):
raise TimeoutError("Function timeout! - {0}s".format(secs))
if secs <= 0:
secs = 0
raise_timeout()
signal.signal(signalnum=signal.SIGALRM, handler=raise_timeout)
signal.alarm(secs)
try:
yield
except TimeoutError:
pass
finally:
signal.signal(signalnum=signal.SIGALRM, handler=signal.SIG_IGN)'''

0 comments on commit a4da41a

Please sign in to comment.