musix-player 0.1.0__tar.gz

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.
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: musix-player
3
+ Version: 0.1.0
4
+ Summary: A terminal-based music player powered by yt-dlp and mpv.
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: typer
8
+ Requires-Dist: rich
9
+ Requires-Dist: yt-dlp>=2023.10.13
File without changes
File without changes
@@ -0,0 +1,178 @@
1
+ import typer
2
+ from rich.console import Console
3
+ from player import get_stream_url, stream_song, search_songs
4
+ from db import CheckPlaylist, CreatePlaylist, DeletePlaylist, addSongToPlaylist, getPlaylistSongs
5
+
6
+ app = typer.Typer()
7
+ console = Console()
8
+
9
+ @app.callback(invoke_without_command=True)
10
+ def main_callback(ctx: typer.Context):
11
+ """
12
+ A terminal-based music player powered by yt-dlp and mpv.
13
+ """
14
+ if ctx.invoked_subcommand is None:
15
+ logo = r'''
16
+ [bold cyan]
17
+ __ __ _ _ _____ _______ __
18
+ | \/ | | | |/ ____|_ _\ \ / /
19
+ | \ / | | | | (___ | | \ V /
20
+ | |\/| | | | |\___ \ | | > <
21
+ | | | | |__| |____) |_| |_ / . \
22
+ |_| |_|\____/|_____/|_____/_/ \_\
23
+ [/bold cyan]'''
24
+ console.print(logo)
25
+ console.print("[bold green]Welcome to Musix![/bold green] Play your favorite songs directly from the terminal.\n")
26
+ console.print("Getting Started:")
27
+ console.print(" [cyan]musix play \"song name\"[/cyan] - Instantly stream a song.")
28
+ console.print(" [cyan]musix search \"song name\"[/cyan] - Search and select from top results.")
29
+ console.print(" [cyan]musix playlist \"name\"[/cyan] - Create your own playlist.")
30
+ console.print(" [cyan]musix add-song \"song name\"[/cyan] - Add a song to a playlist.")
31
+ console.print(" [cyan]musix play-playlist \"playlist name\" [/cyan] - Play a playlist.")
32
+ console.print("\nRun [green]musix --help[/green] for a full list of commands.\n")
33
+
34
+ @app.command()
35
+ def search(query : str):
36
+ '''Let user search and pick the song'''
37
+
38
+ console.print(f'[cyan]Searching[/cyan] for {query}')
39
+ console.print()
40
+
41
+ result = search_songs(query)
42
+
43
+ if not result :
44
+ console.print(f'[red]No Results found[/red]')
45
+ raise typer.Exit()
46
+
47
+ for i, song in enumerate(result, 1):
48
+ duration = f"{song["duration"] // 60} : {song["duration"] % 60:02d}"
49
+ console.print(f"[cyan]{i}[/cyan] [white]{song['title']}[/white]"
50
+ f"[dim] {song["channel"]} | {duration}[/dim]"
51
+ )
52
+ console.print()
53
+
54
+ console.print()
55
+ choice = typer.prompt("Pick a number ( 0 to cancel )")
56
+
57
+ try :
58
+ choice = int(choice)
59
+
60
+ except ValueError:
61
+ console.print("[red]You don't know how numbers looks like[/red]")
62
+ raise typer.Exit()
63
+
64
+ if choice == 0:
65
+ raise typer.Exit()
66
+
67
+ if choice < 1 or choice > len(result):
68
+ console.print("[red]Seriously, did I gave the option to choose that number?[/red]")
69
+ raise typer.Exit()
70
+
71
+ selected = result[choice - 1]
72
+ console.print(f"\n[green]▶ Playing:[/green] {selected['title']}")
73
+
74
+ url, title, sub_url = get_stream_url(
75
+ f"https://www.youtube.com/watch?v={selected['youtube_id']}"
76
+ )
77
+
78
+ stream_song(url, sub_url)
79
+
80
+ @app.command()
81
+ def play(query : str):
82
+ '''stream a song'''
83
+ console.print(f'[green]> Searching:[/green] {query}')
84
+
85
+ url, title, sub_url = get_stream_url(query)
86
+
87
+ console.print(f'[green]-> Now Playing:[/green] {title}')
88
+ console.print('[dim]Press q to stop[/dim]')
89
+ console.print('[dim]Press p to pause/un-pause[/dim]')
90
+
91
+ stream_song(url, sub_url)
92
+
93
+ @app.command()
94
+ def play_playlist(playlist : str):
95
+ '''Play all songs in a playlist'''
96
+ if not CheckPlaylist(playlist):
97
+ console.print(f"[red]Playlist '{playlist}' does not exist![/red]")
98
+ raise typer.Exit()
99
+
100
+ songs = getPlaylistSongs(playlist)
101
+ if not songs:
102
+ console.print(f"[yellow]Playlist '{playlist}' is empty.[/yellow]")
103
+ raise typer.Exit()
104
+
105
+ console.print(f"[green]Playing Playlist:[/green] {playlist}")
106
+ for song_id, song_name in songs:
107
+ console.print(f"\n[cyan]▶ Now Playing:[/cyan] {song_name}")
108
+ url, title, sub_url = get_stream_url(f"https://www.youtube.com/watch?v={song_id}")
109
+ stream_song(url, sub_url)
110
+
111
+ @app.command()
112
+ def download():
113
+ pass
114
+
115
+ @app.command()
116
+ def history():
117
+ pass
118
+
119
+ @app.command()
120
+ def playlist(name : str):
121
+ if not CheckPlaylist(name) :
122
+ if CreatePlaylist(name):
123
+ console.print(f"[green]{name}[/green] Created Successfully !")
124
+ else :
125
+ console.print("Playlist Already Created!")
126
+
127
+ @app.command()
128
+ def deletePlaylist(name : str):
129
+ if DeletePlaylist(name):
130
+ console.print(f"[red]{name}[/red] successfully deleted!")
131
+ else :
132
+ console.print("[yellow]ERROR[/yellow]")
133
+
134
+ @app.command()
135
+ def addSong(name : str, playlist : str):
136
+ searched_songs = search_songs(name)
137
+
138
+ '''Print searched_songs'''
139
+
140
+ console.print(f"[cyan]Searching for[/cyan] {name}")
141
+ i = 1
142
+ store = list()
143
+ for song_details in searched_songs:
144
+ console.print(f"[green]{i}[/green] {song_details["title"]} - {song_details["duration"]//60}:{song_details["duration"]%60:02d} ")
145
+ store.append({song_details["youtube_id"] : [song_details["title"], song_details["duration"], song_details["channel"]]})
146
+ i += 1
147
+ i -= 1
148
+
149
+ console.print()
150
+ choice = typer.prompt(f"Pick a number from 1 to {i} to add the song: ")
151
+
152
+ try:
153
+ choice = int(choice)
154
+ except ValueError:
155
+ console.print(f"[yellow]Enter a valid number[/yellow]")
156
+ typer.Exit()
157
+
158
+ if choice == 0:
159
+ raise typer.Exit()
160
+ elif choice >= i:
161
+ console.print(f"[yellow]Enter a valid number[/yellow]")
162
+ else :
163
+ (song_id, [song_title, song_duration, song_channel]), = store[choice-1].items()
164
+ console.print(f"[green]Selected[/green] : {song_title}")
165
+
166
+ song_duration = int(song_duration)
167
+ addSongToPlaylist(song_title, playlist, song_id, song_duration, song_channel)
168
+
169
+
170
+
171
+
172
+
173
+
174
+ def main():
175
+ app()
176
+
177
+ if __name__ == "__main__":
178
+ main()
@@ -0,0 +1,77 @@
1
+ import sqlite3
2
+
3
+ con = sqlite3.connect("musix.db")
4
+
5
+ cur = con.cursor()
6
+
7
+ cur.execute('''
8
+ CREATE TABLE IF NOT EXISTS playlist(
9
+ PlayID INTEGER PRIMARY KEY,
10
+ PlaylistName varchar(100)
11
+ )''')
12
+
13
+ con.commit()
14
+
15
+ cur.execute('''
16
+
17
+ CREATE TABLE IF NOT EXISTS songs(
18
+ SongID INT PRIMARY KEY,
19
+ SongName VARCHAR(100),
20
+ SongPlayTime INT,
21
+ SongArtist VARCHAR(100),
22
+ SongOrder INT,
23
+ PlayID INT,
24
+ FOREIGN KEY (PlayID) REFERENCES playlist(PlayID)
25
+ ) ''')
26
+
27
+ con.commit()
28
+
29
+ def CheckPlaylist(playlist) -> bool:
30
+ '''CHECKS FOR PLAYLIST NAME IN DATABASE, RETURNS BOOLEAN'''
31
+ exists = cur.execute("SELECT EXISTS(SELECT 1 FROM playlist where PlaylistName = (?))", (playlist,),).fetchone()
32
+ return (bool(exists[0]))
33
+
34
+
35
+ def CreatePlaylist(playlist : str):
36
+ con.execute('''INSERT INTO playlist (PlaylistName) VALUES (?)''', (playlist,))
37
+ con.commit()
38
+ return True
39
+
40
+ def addSongToPlaylist(name : str, playlist : str, song_id : str, song_duration : int, song_channel : str):
41
+
42
+ PlayID = cur.execute("SELECT PlayID from playlist WHERE PlaylistName = (?)", (playlist,)).fetchone()
43
+ if PlayID:
44
+ try :
45
+ cur.execute('''
46
+ INSERT INTO songs (SongID, SongName, SongPlayTime, SongArtist, PlayID) VALUES (?,?,?,?,?)
47
+ ''', (song_id, name, song_duration, song_channel, PlayID[0]))
48
+ except sqlite3.IntegrityError as e:
49
+ print()
50
+ print(f"{name} is already added in {playlist}!")
51
+ else :
52
+ print()
53
+ print(f"Song added successfully!")
54
+ con.commit()
55
+
56
+ def DeletePlaylist(playlist : str):
57
+ res = con.execute('''DELETE FROM playlist WHERE PlaylistName = (?)''', (playlist,))
58
+ con.commit()
59
+
60
+ return res.rowcount > 0
61
+
62
+ def getPlaylistSongs(playlist : str) -> list:
63
+ playID = con.execute("SELECT PlayID from playlist WHERE PlaylistName = (?)", (playlist,)).fetchone()
64
+ if playID:
65
+ res = con.execute("SELECT SongID, SongName from songs WHERE PlayID = (?)", (playID[0],)).fetchall()
66
+ return res
67
+ return []
68
+
69
+ def testing(playlist : str):
70
+ playID = con.execute("SELECT PlayID from playlist WHERE PlaylistName = (?)", (playlist,)).fetchone()
71
+ #res = con.execute("SELECT SongName from songs WHERE PlayID = (?)", (playid,)).fetchall()
72
+ res = con.execute("SELECT SongName from songs WHERE PlayID = (?)", (playID[0],)).fetchall()
73
+ for i in res:
74
+ print(i)
75
+
76
+ if __name__ == "__main__":
77
+ testing("test01")
@@ -0,0 +1,95 @@
1
+ import yt_dlp
2
+ import subprocess
3
+
4
+ def get_stream_url(query : str) -> tuple[str,str,str]:
5
+ '''Inputs : format, mode of stdout, playlist preference, and searching site'''
6
+
7
+ ydl_opts = {
8
+ "format" : "bestaudio",
9
+ "quiet" : True,
10
+ "no_warnings" : True,
11
+ "noplaylist" : True,
12
+ "default_search" : "ytsearch1",
13
+ "writesubtitles" : True,
14
+ "writeautomaticsub" : True,
15
+ "subtitleslangs" : ["en", "HI"]
16
+ # "quiet" : True,
17
+ # "noplaylist" : True,
18
+ # "default_search" : "ytsearch1",
19
+ # "no_warnings" : True
20
+ }
21
+
22
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
23
+ search_query = query if query.startswith(("http://", "https://")) else f"ytsearch1:{query}"
24
+ info = ydl.extract_info(search_query, download=False)
25
+
26
+ if "entries" in info:
27
+ entry = info["entries"][0]
28
+ else:
29
+ entry = info
30
+
31
+ # entries = list(info["entries"])
32
+ # entry = entries[0]
33
+
34
+ sub_url = None
35
+ subs = entry.get("subtitles") or entry.get("automatic_captions") or {}
36
+
37
+ if "en" in subs:
38
+ for sub in subs["en"]:
39
+ if sub.get("ext") == "vtt":
40
+ sub_url = sub["url"]
41
+ break
42
+
43
+ if "requested_formats" in entry:
44
+ url = entry["requested_formats"][0]["url"]
45
+ else:
46
+ formats = entry["formats"]
47
+ audio_formats = [f for f in formats if f.get("acodec") != "none" and f.get("vcodec") == "none"]
48
+ best = max(audio_formats, key=lambda f : f.get("abr") or 0)
49
+ url = best["url"]
50
+ return url, entry["title"], sub_url
51
+
52
+
53
+ def stream_song(url : str, sub_url : str = None):
54
+ '''stream audio using mpv.'''
55
+ cmd = [
56
+ "mpv",
57
+ "--video=no",
58
+ "--terminal=yes",
59
+ "--msg-level=all=error,statusline=status"
60
+ ]
61
+
62
+ if sub_url:
63
+ cmd.append(f"--sub-file={sub_url}")
64
+
65
+ cmd.append(url)
66
+
67
+ subprocess.run(cmd)
68
+
69
+ def search_songs(query : str) -> list[dict]:
70
+ '''yt-dlp fetches top 5 results
71
+ display as a list
72
+ user selects a number
73
+ '''
74
+
75
+ ydl_opts = {
76
+ 'quiet' : True,
77
+ "no_warnings" : True,
78
+ "noplaylist" : True,
79
+ "default_search" : "ytsearch5"
80
+ }
81
+
82
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
83
+ search_query = query if query.startswith(("http://", "https://")) else f"ytsearch5:{query}"
84
+ info = ydl.extract_info(search_query, download=False)
85
+ entries = list(info["entries"])
86
+ result = []
87
+ for entry in entries:
88
+ result.append({
89
+ "title" : entry["title"],
90
+ "youtube_id" : entry["id"],
91
+ "duration" : entry.get("duration", 0),
92
+ "channel" : entry.get("channel", "Unknown"),
93
+ })
94
+
95
+ return result
File without changes
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: musix-player
3
+ Version: 0.1.0
4
+ Summary: A terminal-based music player powered by yt-dlp and mpv.
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: typer
8
+ Requires-Dist: rich
9
+ Requires-Dist: yt-dlp>=2023.10.13
@@ -0,0 +1,13 @@
1
+ README.md
2
+ pyproject.toml
3
+ musix/__init__.py
4
+ musix/cli.py
5
+ musix/db.py
6
+ musix/player.py
7
+ musix/playlist.py
8
+ musix_player.egg-info/PKG-INFO
9
+ musix_player.egg-info/SOURCES.txt
10
+ musix_player.egg-info/dependency_links.txt
11
+ musix_player.egg-info/entry_points.txt
12
+ musix_player.egg-info/requires.txt
13
+ musix_player.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ musix = musix.cli:main
@@ -0,0 +1,3 @@
1
+ typer
2
+ rich
3
+ yt-dlp>=2023.10.13
@@ -0,0 +1,18 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "musix-player"
7
+ version = "0.1.0"
8
+ description = "A terminal-based music player powered by yt-dlp and mpv."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ dependencies = [
12
+ "typer",
13
+ "rich",
14
+ "yt-dlp>=2023.10.13"
15
+ ]
16
+
17
+ [project.scripts]
18
+ musix = "musix.cli:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+