I’ve been talking with my friend about the idea of standalone YouTube music player in Linux, since we are using YouTube to listen to music very often. There is great wealth of good quality tunes published there and easy access to them, just few clicks away makes YouTube a nice candidate for streaming music. My friend suggested that there should be a plugin for Amarok or any other player in Linux which allows you to enqueue some tracks from YouTube and let you play the music, without the burden of running browser.
I said that’s really not a problem since we are Python programmers and quickly thought I might try to quickly hack a proof of concept. After just under an hour, I’ve come up with a quick, dirty hack that allows you to stream music videos from YouTube as MP3.
So what I’ve done is write a really simple and basic script that would conform to the Unix philosophy – one program, one function – under Python of course. The script takes an argument with YouTube video ID and then streams it back to standard out (stdout). Here’s the script:
— cut —
# -*- coding: utf-8 -*- import urllib import sys class AppURLopener(urllib.FancyURLopener): version = "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101230 Mandriva Linux/1.9.2.13-0.2mdv2010.2 (2010.2) Firefox/3.6.13" if len(sys.argv) < 2: print 'Usage: {prog} URL'.format(prog = sys.argv[0]) sys.exit(1) video_id = sys.argv[1] opener = AppURLopener() fp = opener.open('http://www.youtube.com/get_video_info?video_id={vid}'.format(vid = video_id)) data = fp.read() fp.close() if data.startswith('status=fail'): print 'Error: Video not found!' sys.exit(2) vid_list = [] tmp_list = urllib.unquote(urllib.unquote(data)).split('|') for fmt_chk in tmp_list: if len(fmt_chk) == 0: continue if not fmt_chk.startswith('http://'): continue vid_list.append(fmt_chk) # FIXME: Format choice link = vid_list[0] fp = opener.open(link) data = fp.read(1024) while data: sys.stdout.write(data) data = fp.read(1024) fp.close()
— cut —
This script streams the raw video to stdout. It doesn’t have any error corrections and is really simple. If YouTube decides to change the description file it won’t work anymore. Nevertheless you can stream your video to stdout and under Linux you can take care of that stream with available tools.
So I have used FFMPEG transcoder that would rip out on the fly the audio stream, transcode it to MP3 that you can write or listen to if you want.
How can you do this? Pretty simple, given you’ve got necessary decoders installed (mainly FAAC, FAAD and others used in conjunction with libavcodec). If you want to listen to mp3 stream here’s how you do it:
python script.py YOUTUBE_VIDEO_ID | ffmpeg -i - -ab 192k -vn -acodec libmp3lame -f mp3 - | mpg123 -
Where YOUTUBE_VIDEO_ID is the identificator of YouTube video. What FFMPEG options mean:
- “-i -” – that means the input is stdin (standard input)
- “-ab 192k” – means you want to transcode to 192kbit/s mp3 stream
- “-vn” – you don’t want to stream any video
- “-acodec libmp3lame” – use LAME MP3 encoder for the trancoding process
- “-f mp3” – output format should be MP3
- “-” – at the end, means that output is to stdout (standard out)
Due to pipelining you use three programs which make all of this possible. First the streaming script I have written, then the transcoder (ffmpeg) to mp3 stream, then the player (in this case mpg123). If you want to download YouTube video music and save it as MP3 file you do:
python script.py YOUTUBE_VIDEO_ID > stream.flv; ffmpeg -i stream.flv -ab 192k -vn -acodec libmp3lame -f mp3 OUTFILE.MP3
Where you have to substitute YOUTUBE_VIDEO_ID with YouTube video identificator and OUTFILE.MP3 with name of your new MP3 file.
That’s all! Hope you find it useful.
Hi, thank you for your script, I was looking for something similar for months.
Unfortunately I received this error:
“Traceback (most recent call last):
File “main.py”, line 33, in
link = vid_list[0]
IndexError: list index out of range”
with this cmd:
“python main.py UW1nWBquc7M | ffmpeg -i – -ab 192k -vn -acodec libmp3lame -f mp3 – | mpg123 -”
Probably there is something wrong in the get_video_info parsing. Could you give me an example of the correct input necessary to fffmpeg?
Thank you again
Giuseppe
Hi. It was busy month for me recently, so I apologise for the delay. This script was written a long time ago, so it’s probably outdated. I am not aware how youtube changed their XML info about the video. I’ve done a quick check and this get_video_info result are GET parameters that are probably feed to javascript by means of AJAX.
By a quick glance there is a parameter url_encoded_fmt_stream_map and that’s where you should start looking. All that python script does is check get_video_info and then download Flash Video (FLV) stream for output on stdout. This stdout is then piped to ffmpeg’s stdin.
This is all I can do to help you since I don’t have time for researching this topic further, but you should be able to change the script according to your needs and make it work again.
Hi; first of thanks for the script even though the technique used to parse the information
no longer works you solved the hard part. Secondly I fixed the bug with parsing the
information and would like to post it here (hope that’s alright) so it could be of some help
for others wanting to use this script.
# -*- coding: utf-8 -*-
import urllib
import sys
class AppURLopener(urllib.FancyURLopener):
version = “Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101230 Mandriva Linux/1.9.2.13-0.2mdv2010.2 (2010.2) Firefox/3.6.13”
if len(sys.argv) < 2:
print 'Usage: {prog} URL'.format(prog = sys.argv[0])
sys.exit(1)
video_id = sys.argv[1]
opener = AppURLopener()
fp = opener.open('http://www.youtube.com/get_video_info?video_id={vid}'.format(vid = video_id))
data = urllib.unquote(urllib.unquote(fp.read()))
fp.close()
if data.startswith('status=fail'):
print 'Error: Video not found!'
sys.exit(2)
link = data[data.find('url=http://')+4:]
link = link[:link.find('"')]
fp = opener.open(link)
data = fp.read(1024)
while data:
sys.stdout.write(data)
data = fp.read(1024)
fp.close()
Pingback: Using Python to make a terminal-based YouTube audio streamer - BlogoSfera
Hey! thanks for the script 😀
I get this error ..any help plz 🙂
Happy new year btw !!
sldb@sldb-Alternative:~/Documents$ python youtube.py zmyVvrdZN0c | ffmpeg -i – -ab 192k -vn -acodec libmp3lame -f mp3 – | mpg123 –
High Performance MPEG 1.0/2.0/2.5 Audio Player for Layer 1, 2, and 3.
Version 0.3.2-1 (2012/03/25). Written and copyrights by Joe Drew,
now maintained by Nanakos Chrysostomos and others.
Uses code from various people. See ‘README’ for more!
THIS SOFTWARE COMES WITH ABSOLUTELY NO WARRANTY! USE AT YOUR OWN RISK!
ffmpeg version N-77455-g4707497 Copyright (c) 2000-2015 the FFmpeg developers
built with gcc 4.8 (Ubuntu 4.8.4-2ubuntu1~14.04)
configuration: –extra-libs=-ldl –prefix=/opt/ffmpeg –mandir=/usr/share/man –enable-avresample –disable-debug –enable-nonfree –enable-gpl –enable-version3 –enable-libopencore-amrnb –enable-libopencore-amrwb –disable-decoder=amrnb –disable-decoder=amrwb –enable-libpulse –enable-libdcadec –enable-libfreetype –enable-libx264 –enable-libx265 –enable-libfdk-aac –enable-libvorbis –enable-libmp3lame –enable-libopus –enable-libvpx –enable-libspeex –enable-libass –enable-avisynth –enable-libsoxr –enable-libxvid –enable-libvo-aacenc –enable-libvidstab
libavutil 55. 11.100 / 55. 11.100
libavcodec 57. 20.100 / 57. 20.100
libavformat 57. 20.100 / 57. 20.100
libavdevice 57. 0.100 / 57. 0.100
libavfilter 6. 21.101 / 6. 21.101
libavresample 3. 0. 0 / 3. 0. 0
libswscale 4. 0.100 / 4. 0.100
libswresample 2. 0.101 / 2. 0.101
libpostproc 54. 0.100 / 54. 0.100
–: No such file or directory
tcgetattr(): Inappropriate ioctl for device
Playing MPEG stream from – …
[0:00] Decoding of – finished.