lyrics-transcriber 0.30.0__py3-none-any.whl → 0.30.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lyrics_transcriber/cli/{main.py → cli_main.py} +15 -3
- lyrics_transcriber/core/controller.py +129 -95
- lyrics_transcriber/correction/base_strategy.py +29 -0
- lyrics_transcriber/correction/corrector.py +52 -0
- lyrics_transcriber/correction/strategy_diff.py +263 -0
- lyrics_transcriber/lyrics/base_lyrics_provider.py +201 -0
- lyrics_transcriber/lyrics/genius.py +70 -0
- lyrics_transcriber/lyrics/spotify.py +82 -0
- lyrics_transcriber/output/generator.py +158 -97
- lyrics_transcriber/output/subtitles.py +12 -12
- lyrics_transcriber/storage/dropbox.py +110 -134
- lyrics_transcriber/transcribers/audioshake.py +170 -105
- lyrics_transcriber/transcribers/base_transcriber.py +186 -0
- lyrics_transcriber/transcribers/whisper.py +268 -133
- {lyrics_transcriber-0.30.0.dist-info → lyrics_transcriber-0.30.1.dist-info}/METADATA +1 -1
- lyrics_transcriber-0.30.1.dist-info/RECORD +25 -0
- lyrics_transcriber-0.30.1.dist-info/entry_points.txt +3 -0
- lyrics_transcriber/core/corrector.py +0 -56
- lyrics_transcriber/core/fetcher.py +0 -143
- lyrics_transcriber/storage/tokens.py +0 -116
- lyrics_transcriber/transcribers/base.py +0 -31
- lyrics_transcriber-0.30.0.dist-info/RECORD +0 -22
- lyrics_transcriber-0.30.0.dist-info/entry_points.txt +0 -3
- {lyrics_transcriber-0.30.0.dist-info → lyrics_transcriber-0.30.1.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.30.0.dist-info → lyrics_transcriber-0.30.1.dist-info}/WHEEL +0 -0
@@ -1,143 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import logging
|
3
|
-
import lyricsgenius
|
4
|
-
import requests
|
5
|
-
from typing import Optional, Dict, Any
|
6
|
-
|
7
|
-
|
8
|
-
class LyricsFetcher:
|
9
|
-
"""Handles fetching lyrics from various online sources."""
|
10
|
-
|
11
|
-
def __init__(
|
12
|
-
self, genius_api_token: Optional[str] = None, spotify_cookie: Optional[str] = None, logger: Optional[logging.Logger] = None
|
13
|
-
):
|
14
|
-
self.logger = logger or logging.getLogger(__name__)
|
15
|
-
self.genius_api_token = genius_api_token or os.getenv("GENIUS_API_TOKEN")
|
16
|
-
self.spotify_cookie = spotify_cookie or os.getenv("SPOTIFY_COOKIE")
|
17
|
-
|
18
|
-
# Initialize Genius API client if token provided
|
19
|
-
self.genius = None
|
20
|
-
if self.genius_api_token:
|
21
|
-
self.genius = lyricsgenius.Genius(self.genius_api_token)
|
22
|
-
self.genius.verbose = False
|
23
|
-
self.genius.remove_section_headers = True
|
24
|
-
|
25
|
-
def fetch_lyrics(self, artist: str, title: str) -> Dict[str, Any]:
|
26
|
-
"""
|
27
|
-
Fetch lyrics from all available sources.
|
28
|
-
|
29
|
-
Args:
|
30
|
-
artist: Name of the artist
|
31
|
-
title: Title of the song
|
32
|
-
|
33
|
-
Returns:
|
34
|
-
Dict containing:
|
35
|
-
- genius_lyrics: Lyrics from Genius (if available)
|
36
|
-
- spotify_lyrics: Lyrics from Spotify (if available)
|
37
|
-
- source: The preferred source ("genius" or "spotify")
|
38
|
-
- lyrics: The best lyrics found from any source
|
39
|
-
"""
|
40
|
-
self.logger.info(f"Fetching lyrics for {artist} - {title}")
|
41
|
-
|
42
|
-
result = {"genius_lyrics": None, "spotify_lyrics": None, "source": None, "lyrics": None}
|
43
|
-
|
44
|
-
# Try Genius first
|
45
|
-
if self.genius:
|
46
|
-
try:
|
47
|
-
result["genius_lyrics"] = self._fetch_from_genius(artist, title)
|
48
|
-
if result["genius_lyrics"]:
|
49
|
-
result["source"] = "genius"
|
50
|
-
result["lyrics"] = result["genius_lyrics"]
|
51
|
-
except Exception as e:
|
52
|
-
self.logger.error(f"Failed to fetch lyrics from Genius: {str(e)}")
|
53
|
-
|
54
|
-
# Try Spotify if Genius failed or wasn't available
|
55
|
-
if self.spotify_cookie and not result["lyrics"]:
|
56
|
-
try:
|
57
|
-
result["spotify_lyrics"] = self._fetch_from_spotify(artist, title)
|
58
|
-
if result["spotify_lyrics"]:
|
59
|
-
result["source"] = "spotify"
|
60
|
-
result["lyrics"] = result["spotify_lyrics"]
|
61
|
-
except Exception as e:
|
62
|
-
self.logger.error(f"Failed to fetch lyrics from Spotify: {str(e)}")
|
63
|
-
|
64
|
-
return result
|
65
|
-
|
66
|
-
def _fetch_from_genius(self, artist: str, title: str) -> Optional[str]:
|
67
|
-
"""Fetch lyrics from Genius."""
|
68
|
-
self.logger.info(f"Searching Genius for {artist} - {title}")
|
69
|
-
|
70
|
-
try:
|
71
|
-
song = self.genius.search_song(title, artist)
|
72
|
-
if song:
|
73
|
-
self.logger.info("Found lyrics on Genius")
|
74
|
-
return song.lyrics
|
75
|
-
except Exception as e:
|
76
|
-
self.logger.error(f"Error fetching from Genius: {str(e)}")
|
77
|
-
|
78
|
-
return None
|
79
|
-
|
80
|
-
def _fetch_from_spotify(self, artist: str, title: str) -> Optional[str]:
|
81
|
-
"""
|
82
|
-
Fetch lyrics from Spotify.
|
83
|
-
|
84
|
-
Uses the Spotify cookie to authenticate and fetch lyrics for a given song.
|
85
|
-
The cookie can be obtained by logging into Spotify Web Player and copying
|
86
|
-
the 'sp_dc' cookie value.
|
87
|
-
"""
|
88
|
-
self.logger.info(f"Searching Spotify for {artist} - {title}")
|
89
|
-
|
90
|
-
if not self.spotify_cookie:
|
91
|
-
self.logger.warning("No Spotify cookie provided, skipping Spotify lyrics fetch")
|
92
|
-
return None
|
93
|
-
|
94
|
-
try:
|
95
|
-
# First, search for the track
|
96
|
-
search_url = "https://api.spotify.com/v1/search"
|
97
|
-
headers = {
|
98
|
-
"Cookie": f"sp_dc={self.spotify_cookie}",
|
99
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
100
|
-
"App-Platform": "WebPlayer",
|
101
|
-
}
|
102
|
-
params = {"q": f"artist:{artist} track:{title}", "type": "track", "limit": 1}
|
103
|
-
|
104
|
-
self.logger.debug("Making Spotify search request")
|
105
|
-
response = requests.get(search_url, headers=headers, params=params)
|
106
|
-
response.raise_for_status()
|
107
|
-
|
108
|
-
search_data = response.json()
|
109
|
-
if not search_data.get("tracks", {}).get("items"):
|
110
|
-
self.logger.warning("No tracks found on Spotify")
|
111
|
-
return None
|
112
|
-
|
113
|
-
track = search_data["tracks"]["items"][0]
|
114
|
-
track_id = track["id"]
|
115
|
-
|
116
|
-
# Then, fetch lyrics for the track
|
117
|
-
lyrics_url = f"https://api.spotify.com/v1/tracks/{track_id}/lyrics"
|
118
|
-
|
119
|
-
self.logger.debug("Making Spotify lyrics request")
|
120
|
-
lyrics_response = requests.get(lyrics_url, headers=headers)
|
121
|
-
lyrics_response.raise_for_status()
|
122
|
-
|
123
|
-
lyrics_data = lyrics_response.json()
|
124
|
-
if not lyrics_data.get("lyrics", {}).get("lines"):
|
125
|
-
self.logger.warning("No lyrics found for track on Spotify")
|
126
|
-
return None
|
127
|
-
|
128
|
-
# Combine all lines into a single string
|
129
|
-
lyrics_lines = [line["words"] for line in lyrics_data["lyrics"]["lines"] if line.get("words")]
|
130
|
-
lyrics = "\n".join(lyrics_lines)
|
131
|
-
|
132
|
-
self.logger.info("Successfully fetched lyrics from Spotify")
|
133
|
-
return lyrics
|
134
|
-
|
135
|
-
except requests.exceptions.RequestException as e:
|
136
|
-
self.logger.error(f"Error making request to Spotify: {str(e)}")
|
137
|
-
return None
|
138
|
-
except KeyError as e:
|
139
|
-
self.logger.error(f"Unexpected response format from Spotify: {str(e)}")
|
140
|
-
return None
|
141
|
-
except Exception as e:
|
142
|
-
self.logger.error(f"Unexpected error fetching from Spotify: {str(e)}")
|
143
|
-
return None
|
@@ -1,116 +0,0 @@
|
|
1
|
-
#! /usr/bin/env python3
|
2
|
-
import http.server
|
3
|
-
import socketserver
|
4
|
-
import webbrowser
|
5
|
-
import urllib.parse
|
6
|
-
import requests
|
7
|
-
from dotenv import load_dotenv
|
8
|
-
import base64
|
9
|
-
from threading import Thread
|
10
|
-
import argparse
|
11
|
-
|
12
|
-
# Load environment variables
|
13
|
-
load_dotenv()
|
14
|
-
|
15
|
-
REDIRECT_URL = "http://localhost:53682/"
|
16
|
-
|
17
|
-
# Store the authorization code when received
|
18
|
-
auth_code = None
|
19
|
-
|
20
|
-
|
21
|
-
class OAuthHandler(http.server.SimpleHTTPRequestHandler):
|
22
|
-
def do_GET(self):
|
23
|
-
global auth_code
|
24
|
-
# Parse the query parameters
|
25
|
-
query = urllib.parse.urlparse(self.path).query
|
26
|
-
params = urllib.parse.parse_qs(query)
|
27
|
-
|
28
|
-
if "code" in params:
|
29
|
-
auth_code = params["code"][0]
|
30
|
-
# Send success response
|
31
|
-
self.send_response(200)
|
32
|
-
self.send_header("Content-type", "text/html")
|
33
|
-
self.end_headers()
|
34
|
-
self.wfile.write(b"Authorization successful! You can close this window.")
|
35
|
-
|
36
|
-
# Shutdown the server
|
37
|
-
Thread(target=self.server.shutdown).start()
|
38
|
-
else:
|
39
|
-
# Handle error or other cases
|
40
|
-
self.send_response(400)
|
41
|
-
self.send_header("Content-type", "text/html")
|
42
|
-
self.end_headers()
|
43
|
-
self.wfile.write(b"Authorization failed!")
|
44
|
-
|
45
|
-
|
46
|
-
def get_tokens(app_key, app_secret):
|
47
|
-
# Construct the authorization URL
|
48
|
-
auth_url = (
|
49
|
-
"https://www.dropbox.com/oauth2/authorize"
|
50
|
-
f"?client_id={app_key}"
|
51
|
-
f"&redirect_uri={REDIRECT_URL}"
|
52
|
-
"&response_type=code"
|
53
|
-
"&token_access_type=offline"
|
54
|
-
)
|
55
|
-
|
56
|
-
# Start local server
|
57
|
-
port = int(REDIRECT_URL.split(":")[-1].strip("/"))
|
58
|
-
httpd = socketserver.TCPServer(("", port), OAuthHandler)
|
59
|
-
|
60
|
-
print(f"Opening browser for Dropbox authorization...")
|
61
|
-
webbrowser.open(auth_url)
|
62
|
-
|
63
|
-
print(f"Waiting for authorization...")
|
64
|
-
httpd.serve_forever()
|
65
|
-
|
66
|
-
if auth_code:
|
67
|
-
print("Authorization code received, exchanging for tokens...")
|
68
|
-
|
69
|
-
# Exchange authorization code for tokens
|
70
|
-
auth = base64.b64encode(f"{app_key}:{app_secret}".encode()).decode()
|
71
|
-
response = requests.post(
|
72
|
-
"https://api.dropbox.com/oauth2/token",
|
73
|
-
data={"code": auth_code, "grant_type": "authorization_code", "redirect_uri": REDIRECT_URL},
|
74
|
-
headers={"Authorization": f"Basic {auth}"},
|
75
|
-
)
|
76
|
-
|
77
|
-
if response.status_code == 200:
|
78
|
-
tokens = response.json()
|
79
|
-
print("\nTokens received successfully!")
|
80
|
-
print("\nAdd these lines to your .env file:")
|
81
|
-
print(f"WHISPER_DROPBOX_APP_KEY={app_key}")
|
82
|
-
print(f"WHISPER_DROPBOX_APP_SECRET={app_secret}")
|
83
|
-
print(f"WHISPER_DROPBOX_ACCESS_TOKEN={tokens['access_token']}")
|
84
|
-
print(f"WHISPER_DROPBOX_REFRESH_TOKEN={tokens['refresh_token']}")
|
85
|
-
|
86
|
-
# Optionally update .env file directly
|
87
|
-
update = input("\nWould you like to update your .env file automatically? (y/n): ")
|
88
|
-
if update.lower() == "y":
|
89
|
-
with open(".env", "r") as f:
|
90
|
-
lines = f.readlines()
|
91
|
-
|
92
|
-
with open(".env", "w") as f:
|
93
|
-
for line in lines:
|
94
|
-
if line.startswith("WHISPER_DROPBOX_APP_KEY="):
|
95
|
-
f.write(f"WHISPER_DROPBOX_APP_KEY={app_key}\n")
|
96
|
-
elif line.startswith("WHISPER_DROPBOX_APP_SECRET="):
|
97
|
-
f.write(f"WHISPER_DROPBOX_APP_SECRET={app_secret}\n")
|
98
|
-
elif line.startswith("WHISPER_DROPBOX_ACCESS_TOKEN="):
|
99
|
-
f.write(f"WHISPER_DROPBOX_ACCESS_TOKEN={tokens['access_token']}\n")
|
100
|
-
elif line.startswith("WHISPER_DROPBOX_REFRESH_TOKEN="):
|
101
|
-
f.write(f"WHISPER_DROPBOX_REFRESH_TOKEN={tokens['refresh_token']}\n")
|
102
|
-
else:
|
103
|
-
f.write(line)
|
104
|
-
print("Updated .env file successfully!")
|
105
|
-
else:
|
106
|
-
print("Error exchanging authorization code for tokens:")
|
107
|
-
print(response.text)
|
108
|
-
|
109
|
-
|
110
|
-
if __name__ == "__main__":
|
111
|
-
parser = argparse.ArgumentParser(description="Get Dropbox OAuth tokens.")
|
112
|
-
parser.add_argument("--app-key", required=True, help="Dropbox App Key")
|
113
|
-
parser.add_argument("--app-secret", required=True, help="Dropbox App Secret")
|
114
|
-
|
115
|
-
args = parser.parse_args()
|
116
|
-
get_tokens(args.app_key, args.app_secret)
|
@@ -1,31 +0,0 @@
|
|
1
|
-
from abc import ABC, abstractmethod
|
2
|
-
from typing import Dict, Any
|
3
|
-
import logging
|
4
|
-
|
5
|
-
|
6
|
-
class BaseTranscriber(ABC):
|
7
|
-
"""Base class for all transcription services."""
|
8
|
-
|
9
|
-
def __init__(self, logger: logging.Logger = None):
|
10
|
-
self.logger = logger or logging.getLogger(__name__)
|
11
|
-
|
12
|
-
@abstractmethod
|
13
|
-
def transcribe(self, audio_filepath: str) -> Dict[str, Any]:
|
14
|
-
"""
|
15
|
-
Transcribe an audio file and return the results in a standardized format.
|
16
|
-
|
17
|
-
Args:
|
18
|
-
audio_filepath (str): Path to the audio file to transcribe
|
19
|
-
|
20
|
-
Returns:
|
21
|
-
Dict containing:
|
22
|
-
- segments: List of segments with start/end times and word-level data
|
23
|
-
- text: Full text transcription
|
24
|
-
- metadata: Dict of additional info (confidence, language, etc)
|
25
|
-
"""
|
26
|
-
pass
|
27
|
-
|
28
|
-
@abstractmethod
|
29
|
-
def get_name(self) -> str:
|
30
|
-
"""Return the name of this transcription service."""
|
31
|
-
pass
|
@@ -1,22 +0,0 @@
|
|
1
|
-
lyrics_transcriber/__init__.py,sha256=Hj2HdSBAl6kmiqa5s3MDo_RobkITadzuF-81-ON3awA,180
|
2
|
-
lyrics_transcriber/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
lyrics_transcriber/cli/main.py,sha256=fCg9LxUZKf9ByelZIpF0XhsTVzXadHIXVL7qMhSDZao,7686
|
4
|
-
lyrics_transcriber/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
lyrics_transcriber/core/controller.py,sha256=t5zohET8_pnRZj7dtCO0jcXXF24DQc_POTrI11IA3pE,11100
|
6
|
-
lyrics_transcriber/core/corrector.py,sha256=_FjelES_9JF2fDP_Rgzg1iYpbQHIKjdG4Za1J5xy3xg,2274
|
7
|
-
lyrics_transcriber/core/fetcher.py,sha256=jUr-eoxjjbheFaR3iVdUiodODiS91fyrtJxTZ35zqIs,5801
|
8
|
-
lyrics_transcriber/output/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
lyrics_transcriber/output/ass.py,sha256=b8lnjgXGD1OD1ld_b1xxUmSOf4nSEfz9BpgSkh16R4g,90291
|
10
|
-
lyrics_transcriber/output/generator.py,sha256=DaWgPMc37Q52StfUFNUmKV9tJHUkL59zYZ_gVacguf8,8052
|
11
|
-
lyrics_transcriber/output/subtitles.py,sha256=_WG0pFoZMXcrGe6gbARkC9KrWzFNTMOsiqQwNL-H2lU,11812
|
12
|
-
lyrics_transcriber/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
-
lyrics_transcriber/storage/dropbox.py,sha256=dYTXuoACY-Ad03OuXLEyST-8L2TFZ0QahP2KulZH-54,11307
|
14
|
-
lyrics_transcriber/storage/tokens.py,sha256=t7TdX12VjemklaCq0sgHfSEbLYx9_e15nRc5T5C0Ar8,4378
|
15
|
-
lyrics_transcriber/transcribers/audioshake.py,sha256=reI_yven65Vq0Kpjl2QupxWo1yRg57rR4LI-qRMD1mY,6154
|
16
|
-
lyrics_transcriber/transcribers/base.py,sha256=DzZRrxbWaKUzNtOyD58ggrZrcmJvXAbowOLuH6Lclto,981
|
17
|
-
lyrics_transcriber/transcribers/whisper.py,sha256=rtvmG9T0MO4_8et7uw1XwyGc2k81mwGdGk4ghqdEvI0,6852
|
18
|
-
lyrics_transcriber-0.30.0.dist-info/LICENSE,sha256=BiPihPDxhxIPEx6yAxVfAljD5Bhm_XG2teCbPEj_m0Y,1069
|
19
|
-
lyrics_transcriber-0.30.0.dist-info/METADATA,sha256=qRLSYfuIJDG1E77YBB6-QfYH6gP10knQzbdRROVyBog,5485
|
20
|
-
lyrics_transcriber-0.30.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
21
|
-
lyrics_transcriber-0.30.0.dist-info/entry_points.txt,sha256=_pPAHBMByKbWN-6RCscyJUYXTd3iVI1m-zzV2Sp9HV0,71
|
22
|
-
lyrics_transcriber-0.30.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|