CoolFace
Apppublic

hardbanrecords/Metadata-Engine

sourceHugging Faceotherupdated 8mo agoView on Hugging Face
0likes
mir.py222 linesDownload Raw Back to services
1import os2try:3    import numpy as np4except ImportError:5    np = None6import logging7 8try:9    import librosa10    import numpy as np11except ImportError:12    # Local analysis disabled13    librosa = None14    # numpy might be missing too if librosa is missing, handled by earlier lazy loading logic usually15    # but here we imported it at top level. Wait, top level import is at line 2.16    # If numpy is missing, line 2 crashes.17    # But I removed numpy from requirements. So line 2 WILL crash.18    pass19 20try:21    from mutagen.easyid3 import EasyID322    from mutagen.id3 import (23        ID3,24        TIT2,25        TPE1,26        TALB,27        TCON,28        TDRC,29        COMM,30        USLT,31        TPUB,32        TCOP,33        TCOM,34        TEXT,35    )36    from mutagen.wave import WAVE37    from mutagen.mp3 import MP338    from mutagen.flac import FLAC39except ImportError:40    # Should not happen as mutagen is in requirements41    ID3 = None42    MP3 = None43    FLAC = None44    WAVE = None45 46logger = logging.getLogger(__name__)47 48 49class MIRService:50    @staticmethod51    def is_available():52        return librosa is not None53 54    @staticmethod55    async def analyze_audio(file_path: str):56        """57        Uses Librosa to extract technical features from the audio file.58        Returns a dictionary of features.59        """60        if not MIRService.is_available():61            raise RuntimeError("Librosa/Mutagen libraries not installed on backend.")62 63        try:64            # Load audio (only first 60 seconds for performance, unless deep analysis requested)65            # Duration analysis requires full load or stream info.66            y, sr = librosa.load(file_path, duration=120)67 68            # 1. BPM & Beat Tracking69            tempo, _ = librosa.beat.beat_track(y=y, sr=sr)70            bpm = float(tempo)71 72            # 2. Spectral Features (Timbre/Brightness)73            spectral_centroid = np.mean(librosa.feature.spectral_centroid(y=y, sr=sr))74            spectral_rolloff = np.mean(librosa.feature.spectral_rolloff(y=y, sr=sr))75 76            # 3. Key / Tonality (Simple estimate)77            # Chromecast -> shift to major/minor78            chroma = librosa.feature.chroma_stft(y=y, sr=sr)79            key_idx = np.argmax(np.mean(chroma, axis=1))80            keys = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]81            path_key = keys[key_idx]82 83            # 4. Danceability (Rough estimate based on rhythm stability)84            onset_env = librosa.onset.onset_strength(y=y, sr=sr)85            pulse = librosa.beat.plp(onset_envelope=onset_env, sr=sr)86            danceability = float(87                np.mean(pulse)88            )  # Normalize logic would be needed for 0-189 90            return {91                "bpm": round(bpm, 1),92                "key": path_key,93                "duration": librosa.get_duration(y=y, sr=sr),94                "technical": {95                    "spectral_centroid": float(spectral_centroid),96                    "spectral_rolloff": float(spectral_rolloff),97                    "danceability_score": round(danceability, 2),98                },99            }100        except Exception as e:101            logger.error(f"MIR Analysis Failed: {e}")102            raise e103 104    @staticmethod105    def write_metadata(file_path: str, metadata: dict):106        """107        Writes standard ID3/Vorbis tags using Mutagen.108        Supports MP3, WAV, FLAC.109        """110        if not MIRService.is_available():111            raise RuntimeError("Mutagen library not installed.")112 113        ext = os.path.splitext(file_path)[1].lower()114 115        try:116            audio = None117            if ext == ".mp3":118                audio = MP3(file_path, ID3=ID3)119                try:120                    audio.add_tags()121                except Exception:122                    pass  # Tags might exist123            elif ext == ".wav":124                try:125                    audio = WAVE(file_path)126                    try:127                        audio.add_tags()128                    except Exception:129                        pass130                except Exception:131                    # Some WAVs are weird, standard open might fail132                    return False133            elif ext == ".flac":134                audio = FLAC(file_path)135 136            if audio is None:137                return False138 139            # --- MP3 / WAV (ID3) Mapping ---140            if ext in [".mp3", ".wav"]:141                # Basic142                if "title" in metadata:143                    audio.tags.add(TIT2(encoding=3, text=metadata["title"]))144                if "artist" in metadata:145                    audio.tags.add(TPE1(encoding=3, text=metadata["artist"]))146                if "album" in metadata:147                    audio.tags.add(TALB(encoding=3, text=metadata["album"]))148                if "genre" in metadata:149                    audio.tags.add(TCON(encoding=3, text=metadata["genre"]))150                if "year" in metadata:151                    audio.tags.add(TDRC(encoding=3, text=str(metadata["year"])))152 153                # Technical (BPM & Key) - Local Analysis Results154                from mutagen.id3 import TBPM, TKEY155 156                if "bpm" in metadata and metadata["bpm"]:157                    audio.tags.add(TBPM(encoding=3, text=str(metadata["bpm"])))158                if "key" in metadata and metadata["key"]:159                    audio.tags.add(TKEY(encoding=3, text=str(metadata["key"])))160 161                # Extended (Requested by user)162                if "publisher" in metadata:163                    audio.tags.add(TPUB(encoding=3, text=metadata["publisher"]))164                if "label" in metadata:165                    audio.tags.add(166                        TPUB(encoding=3, text=metadata["label"])167                    )  # Fallback if same168                if "copyright" in metadata:169                    audio.tags.add(TCOP(encoding=3, text=metadata["copyright"]))170                if "composer" in metadata:171                    audio.tags.add(TCOM(encoding=3, text=metadata["composer"]))172                if "lyricist" in metadata:173                    audio.tags.add(TEXT(encoding=3, text=metadata["lyricist"]))174 175                # Lyrics176                if "lyrics" in metadata:177                    audio.tags.add(178                        USLT(179                            encoding=3,180                            lang="eng",181                            desc="Lyrics",182                            text=metadata["lyrics"],183                        )184                    )185 186                audio.save(v2_version=3)  # Force ID3v2.3 for max compatibility187                return True188 189            # --- FLAC (Vorbis) Mapping ---190            if ext == ".flac":191                if "title" in metadata:192                    audio["title"] = metadata["title"]193                if "artist" in metadata:194                    audio["artist"] = metadata["artist"]195                if "album" in metadata:196                    audio["album"] = metadata["album"]197                if "year" in metadata:198                    audio["date"] = str(metadata["year"])199                if "genre" in metadata:200                    audio["genre"] = metadata["genre"]201                if "copyright" in metadata:202                    audio["copyright"] = metadata["copyright"]203                if "publisher" in metadata:204                    audio["publisher"] = metadata["publisher"] or metadata.get("label")205                if "lyrics" in metadata:206                    audio["lyrics"] = metadata["lyrics"]207 208                # Technical209                if "bpm" in metadata:210                    audio["bpm"] = str(metadata["bpm"])211                if "key" in metadata:212                    audio["initialkey"] = str(213                        metadata["key"]214                    )  # 'initialkey' is common Vorbis field215 216                audio.save()217                return True218 219        except Exception as e:220            logger.error(f"Tagging Failed: {e}")221            raise e222