Last active
December 10, 2021 06:47
-
-
Save rachmadaniHaryono/934d0ba8eea14f3be5e91eb033481518 to your computer and use it in GitHub Desktop.
youtube + beets
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
"""simple script for download youtube mp3 and add to beets. | |
workflow | |
- search title/artist or both from beets | |
- search youtube query | |
- choose and download youtube video | |
- import to beets | |
require | |
- beets | |
- bs4 | |
- pafy | |
- pyav | |
- youtube-dl | |
- vcr (test) | |
""" | |
from __future__ import division, absolute_import, print_function | |
from urllib.parse import quote_plus, urlparse, parse_qs | |
import argparse | |
import os | |
import shutil | |
from beets.autotag import mb | |
from beets.ui import main as beets_main | |
import av | |
import bs4 | |
import click | |
import pafy | |
import requests | |
def convert2mp3(filename): | |
inp = av.open(filename, 'r') | |
ff, _ = os.path.splitext(filename) | |
output_filename = ff + '.mp3' | |
out = av.open(output_filename, 'w') | |
ostream = out.add_stream("mp3") | |
for frame in inp.decode(audio=0): | |
frame.pts = None | |
for p in ostream.encode(frame): | |
out.mux(p) | |
for p in ostream.encode(None): | |
out.mux(p) | |
out.close() | |
return output_filename | |
def search_youtube(query): | |
yt_q_url = 'https://www.youtube.com/results?search_query={}'.format( | |
quote_plus(query)) | |
resp = requests.get(yt_q_url) | |
soup = bs4.BeautifulSoup(resp.text, 'html.parser') | |
hrefs = list(map(lambda x: x.attrs.get('href', None), soup.select('a'))) | |
hrefs_with_qs = list(filter(lambda x: urlparse(x).query, hrefs)) | |
v_parts = list(map( | |
lambda x: parse_qs(urlparse(x).query).get('v', [None])[0], | |
hrefs_with_qs | |
)) | |
v_parts = set(list(filter(lambda x: x, v_parts))) | |
return v_parts | |
def print_youtube_tracks(pafy_objs, sort=False): | |
print('youtube tracks:') | |
yt_vs = pafy_objs | |
if sort: | |
yt_vs.sort(key=lambda x: x.title) | |
for idx, tr in enumerate(yt_vs, 1): | |
m, s = list(map(lambda x: int(x), divmod(tr.length, 60))) | |
kwargs = dict(idx=idx, track=tr, minute=m, second=s) | |
print('[{idx}] {track.title} ({minute}:{second})'.format(**kwargs)) | |
def print_mb_tracks(tracks): | |
mb_tracks = tracks | |
print('Musicbrainz tracks:') | |
for idx, tr in enumerate(mb_tracks, 1): | |
m, s = list(map(lambda x: int(x), divmod(tr.length, 60))) | |
kwargs = dict(idx=idx, track=tr, minute=m, second=s) | |
print( | |
'[{idx}] {track.artist} - {track.title} ({minute}:{second})'.format( | |
**kwargs | |
) | |
) | |
def main(args=None): | |
"""download youtube-dl and import to beets.""" | |
parser = argparse.ArgumentParser() | |
parser.add_argument("--title", help="musicbrainz title query.") | |
parser.add_argument("--artist", help="musicbrainz artis query") | |
parser.add_argument( | |
"--sort-yt", help="sort youtube tracks", action="store_true") | |
parser.add_argument("--query", help="query") | |
args = parser.parse_args(args) | |
if args.query: | |
if not args.title and not args.artist: | |
mb_tracks = mb.match_track('', args.query) | |
else: | |
mb_tracks = mb.match_track(args.artist, args.title) | |
mb_tracks = list(mb_tracks) | |
print_mb_tracks(mb_tracks) | |
v_parts = search_youtube(args.query) | |
yt_vs = list(map(lambda x: pafy.new(x), v_parts)) | |
print_youtube_tracks(yt_vs, sort=args.sort_yt) | |
else: | |
mb_tracks = [] | |
yt_vs = [] | |
exit_flag = False | |
while not exit_flag: | |
user_input = input('input>') | |
keyword = user_input.split(' ')[0] | |
if keyword in ('quit', 'exit', 'q', 'x'): | |
exit_flag = True | |
elif keyword in ('help', 'h'): | |
print( | |
"""Help: | |
(h)elp\t\t\tShow this message. | |
(q)uit/e(x)it\t\tExit program. | |
download <number>\tDownload youtube. | |
search-yt <query>\tRun youtube search. | |
search-mb\t\tRun musicbrainz search. | |
show-yt\t\t\tShow youtube result. | |
show-mb\t\t\tShow musicbrainz result.""" | |
) | |
elif keyword == 'download': | |
if yt_vs: | |
input_val = int(user_input.split(' ')[1]) | |
sel_yt_v = yt_vs[input_val-1] | |
best_audio = sel_yt_v.getbestaudio() | |
import_flag = True | |
if best_audio and best_audio.extension == 'webm': | |
webm_filename = best_audio.download() | |
try: | |
filename = convert2mp3(webm_filename) | |
except UnicodeEncodeError as e: | |
print('{}: {}'.format(type(e), e)) | |
print('renaming file to ascii filename') | |
new_webm_filename = webm_filename.encode('ascii', 'replace').decode() | |
shutil.copy(webm_filename, new_webm_filename) | |
filename = convert2mp3(new_webm_filename) | |
elif best_audio: | |
filename = best_audio.download() | |
else: | |
import_flag = False | |
if import_flag: | |
beets_main(['import', '-s', filename]) | |
else: | |
print('No youtube videos found.') | |
elif keyword == 'search-mb': | |
artist_input = input('artist>') | |
title_input = input('title>') | |
mb_tracks = mb.match_track(artist_input, title_input) | |
print_mb_tracks(mb_tracks) | |
elif keyword == 'search-yt': | |
input_val = user_input.split(' ', 1)[1] | |
v_parts = search_youtube(input_val) | |
yt_vs = list(map(lambda x: pafy.new(x), v_parts)) | |
print_youtube_tracks(yt_vs, sort=args.sort_yt) | |
elif keyword == 'show-yt': | |
if yt_vs: | |
print_youtube_tracks(yt_vs, sort=args.sort_yt) | |
else: | |
print('No youtube videos found.') | |
elif keyword == 'show-mb': | |
if mb_tracks: | |
print_mb_tracks(mb_tracks) | |
else: | |
print('No musicbrainz track found.') | |
else: | |
print('Unknown keyword.') | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I like