From 0ab0c2cd76b7255e2588cc7147a2d550e873518d Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Thu, 30 Jan 2020 19:29:07 +0100 Subject: [PATCH] With generic extractor, infer track name from id3 tags --- poetry.lock | 55 ++++++++++++++++++++++++++++++++++-- pyproject.toml | 3 +- royalnet/bard/ytdldiscord.py | 10 +++++-- royalnet/bard/ytdlfile.py | 19 +++++++++++++ royalnet/bard/ytdlinfo.py | 2 ++ 5 files changed, 83 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1faebc2d..7ec23df1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -158,6 +158,17 @@ pytz = "*" regex = "*" tzlocal = "*" +[[package]] +category = "main" +description = "A library to handle automated deprecations" +name = "deprecation" +optional = false +python-versions = "*" +version = "2.0.7" + +[package.dependencies] +packaging = "*" + [[package]] category = "main" description = "A python wrapper for the Discord API" @@ -182,6 +193,23 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "0.16" +[[package]] +category = "main" +description = "Python audio data toolkit (ID3 and MP3)" +name = "eyed3" +optional = false +python-versions = "*" +version = "0.9" + +[package.dependencies] +deprecation = "*" +filetype = "*" + +[package.extras] +art-plugin = ["pylast", "requests", "pillow"] +display-plugin = ["grako"] +yaml-plugin = ["ruamel.yaml"] + [[package]] category = "main" description = "Python bindings for FFmpeg - with complex filtering support" @@ -196,6 +224,14 @@ future = "*" [package.extras] dev = ["future (0.17.1)", "numpy (1.16.4)", "pytest-mock (1.10.4)", "pytest (4.6.1)", "Sphinx (2.1.0)", "tox (3.12.1)"] +[[package]] +category = "main" +description = "Infer file type and MIME type of any file/buffer. No external dependencies." +name = "filetype" +optional = false +python-versions = "*" +version = "1.0.5" + [[package]] category = "main" description = "Clean single-source support for Python 3 and 2" @@ -377,7 +413,7 @@ python-versions = ">=3.5" version = "4.7.4" [[package]] -category = "dev" +category = "main" description = "Core utilities for Python packages" name = "packaging" optional = false @@ -464,7 +500,7 @@ docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)", "hypothesis (>=3.27.0)"] [[package]] -category = "dev" +category = "main" description = "Python parsing module" name = "pyparsing" optional = false @@ -889,7 +925,7 @@ sentry = ["sentry_sdk"] telegram = ["python_telegram_bot"] [metadata] -content-hash = "6f14bbcfb695def8593da8e916e54a02a09d5a70852aa8ebd360aa03e33d3cd4" +content-hash = "e46d65bd8228040eb92de09eb31a8a0987a1afbc6f39e62656f9a306df049ba4" python-versions = "^3.8" [metadata.files] @@ -1009,6 +1045,10 @@ dateparser = [ {file = "dateparser-0.7.2-py2.py3-none-any.whl", hash = "sha256:983d84b5e3861cb0aa240cad07f12899bb10b62328aae188b9007e04ce37d665"}, {file = "dateparser-0.7.2.tar.gz", hash = "sha256:e1eac8ef28de69a554d5fcdb60b172d526d61924b1a40afbbb08df459a36006b"}, ] +deprecation = [ + {file = "deprecation-2.0.7-py2.py3-none-any.whl", hash = "sha256:dc9b4f252b7aca8165ce2764a71da92a653b5ffbf7a389461d7a640f6536ecb2"}, + {file = "deprecation-2.0.7.tar.gz", hash = "sha256:c0392f676a6146f0238db5744d73e786a43510d54033f80994ef2f4c9df192ed"}, +] "discord.py" = [ {file = "discord.py-1.3.1-py3-none-any.whl", hash = "sha256:8bfe5628d31771744000f19135c386c74ac337479d7282c26cc1627b9d31f360"}, ] @@ -1016,10 +1056,19 @@ docutils = [ {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, ] +eyed3 = [ + {file = "eyeD3-0.9-py2.py3-none-any.whl", hash = "sha256:6015669333df2115809102ccf1b29585115b5c233cf4530d9f995ad562634819"}, + {file = "eyeD3-0.9-py3.8.egg", hash = "sha256:8e3a7a2ce2932260f77c6234d624737807cdb4404f5bac3c5348c1f9da3d7250"}, + {file = "eyeD3-0.9.tar.gz", hash = "sha256:8874762fd4fd93fa64676185ccaa77ea8b3396aea65ba86bca7325f1136f9c8a"}, +] ffmpeg-python = [ {file = "ffmpeg-python-0.2.0.tar.gz", hash = "sha256:65225db34627c578ef0e11c8b1eb528bb35e024752f6f10b78c011f6f64c4127"}, {file = "ffmpeg_python-0.2.0-py3-none-any.whl", hash = "sha256:ac441a0404e053f8b6a1113a77c0f452f1cfc62f6344a769475ffdc0f56c23c5"}, ] +filetype = [ + {file = "filetype-1.0.5-py2.py3-none-any.whl", hash = "sha256:4967124d982a71700d94a08c49c4926423500e79382a92070f5ab248d44fe461"}, + {file = "filetype-1.0.5.tar.gz", hash = "sha256:17a3b885f19034da29640b083d767e0f13c2dcb5dcc267945c8b6e5a5a9013c7"}, +] future = [ {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, ] diff --git a/pyproject.toml b/pyproject.toml index f620448d..2fcc3ef0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ # bard ffmpeg_python = {version="~0.2.0", optional=true} youtube_dl = {version="*", optional=true} + eyed3 = {version="^0.9", optional=true} # alchemy sqlalchemy = {version="^1.3.10", optional=true} @@ -72,7 +73,7 @@ matrix = ["matrix-nio"] alchemy_easy = ["sqlalchemy", "psycopg2_binary"] alchemy_hard = ["sqlalchemy", "psycopg2"] - bard = ["ffmpeg_python", "youtube_dl"] + bard = ["ffmpeg_python", "youtube_dl", "eyed3"] constellation = ["starlette", "uvicorn", "python-multipart"] sentry = ["sentry_sdk"] herald = ["websockets"] diff --git a/royalnet/bard/ytdldiscord.py b/royalnet/bard/ytdldiscord.py index cda4356a..0311d316 100644 --- a/royalnet/bard/ytdldiscord.py +++ b/royalnet/bard/ytdldiscord.py @@ -105,6 +105,7 @@ class YtdlDiscord: "Clyp": 0x3DBEB3, "Bandcamp": 0x1DA0C3, "PeerTube": 0xF1680D, + "generic": 0x4F545C, } embed = discord.Embed(title=self.info.title, colour=discord.Colour(colors.get(self.info.extractor, 0x4F545C)), @@ -114,9 +115,14 @@ class YtdlDiscord: if self.info.uploader: embed.set_author(name=self.info.uploader, url=self.info.uploader_url if self.info.uploader_url is not None else discord.embeds.EmptyEmbed) - # embed.set_footer(text="Source: youtube-dl", icon_url="https://i.imgur.com/TSvSRYn.png") + elif self.info.artist: + embed.set_author(name=self.info.artist, + url=discord.embeds.EmptyEmbed) + if self.info.album: + embed.add_field(name="Album", value=self.info.album, inline=True) if self.info.duration: embed.add_field(name="Duration", value=str(self.info.duration), inline=True) - if self.info.upload_date: + if self.info.extractor != "generic" and self.info.upload_date: embed.add_field(name="Published on", value=self.info.upload_date.strftime("%d %b %Y"), inline=True) + # embed.set_footer(text="Source: youtube-dl", icon_url="https://i.imgur.com/TSvSRYn.png") return embed diff --git a/royalnet/bard/ytdlfile.py b/royalnet/bard/ytdlfile.py index b60d3533..bda35281 100644 --- a/royalnet/bard/ytdlfile.py +++ b/royalnet/bard/ytdlfile.py @@ -1,6 +1,7 @@ import os import logging import re +import eyed3 from contextlib import asynccontextmanager from typing import * from royalnet.utils import * @@ -96,6 +97,24 @@ class YtdlFile: async with self.lock.exclusive(): log.debug(f"Downloading with youtube-dl: {self}") await asyncify(download, loop=self._loop) + if self.info.extractor == "generic": + log.debug(f"Generic extractor detected, updating info from the downloaded file: {self}") + self.set_ytdlinfo_from_id3_tags() + + def set_ytdlinfo_from_id3_tags(self): + tag_file = eyed3.load(self.filename) + if not tag_file: + log.debug(f"No ID3 tags found: {self}") + tag: eyed3.core.Tag = tag_file.tag + if tag.title: + log.debug(f"Found title: {self}") + self.info.title = tag.title + if tag.album: + log.debug(f"Found album: {self}") + self.info.album = tag.album + if tag.artist: + log.debug(f"Found artist: {self}") + self.info.artist = tag.artist @asynccontextmanager async def aopen(self): diff --git a/royalnet/bard/ytdlinfo.py b/royalnet/bard/ytdlinfo.py index 25c59e0e..ec4b0c97 100644 --- a/royalnet/bard/ytdlinfo.py +++ b/royalnet/bard/ytdlinfo.py @@ -86,6 +86,8 @@ class YtdlInfo: self.acodec: Optional[str] = info.get("acodec") self.abr: Optional[int] = info.get("abr") self.ext: Optional[str] = info.get("ext") + # Additional custom information + self.album: Optional[str] = None @classmethod async def from_url(cls, url, loop: Optional[AbstractEventLoop] = None, **ytdl_args) -> List["YtdlInfo"]: