4

I'm new to Python, and have only worked with PHP 5 in the past, (many years ago now). As a beginner project I thought I'd make a YouTube downloader using pytube that lets you choose whether to download a video in highest quality or only download the audio from it as a .mp3.

Well I'm stuck on the last part: changing the extension to .mp3.

I'd like a simple and elegant solution that I can understand, but any help will be appreciated.

I tried using os.rename() but am unsure how to make it work.

from pytube import YouTube
import os

yt = YouTube(str(input("Enter the URL of the video you want to download: \n>> ")))

print("Select download format:")
print("1: Video file with audio (.mp4)")
print("2: Audio only (.mp3)")

media_type = input()

if media_type == "1":
    video = yt.streams.first()

elif media_type == "2":
    video = yt.streams.filter(only_audio = True).first()

else:
    print("Invalid selection.")

print("Enter the destination (leave blank for current directory)")
destination = str(input(">> "))

video.download(output_path = destination)

if media_type == "2":
    os.rename(yt.title + ".mp4", yt.title + ".mp3")

print(yt.title + "\nHas been successfully downloaded.")

EDIT:

It just hung on the last part yesterday when I tried it, but I just tried again and after a while I got this error message:

Traceback (most recent call last):
  File "Tubey.py", line 42, in <module>
    os.rename(yt.title + ".mp4", yt.title + ".mp3")
FileNotFoundError: [WinError 2] The system cannot find the file specified: "Cristobal Tapia de veer - DIRK GENTLY's original score sampler - cut 3.mp4" -> "Cristobal Tapia de veer - DIRK GENTLY's original score sampler - cut 3.mp3"

The file got downloaded but did not get renamed.

FINAL EDIT: (probably)

I finally got it working, mostly thanks to J_H. Thank you for putting up with my incompetence, you're a saint. Here's the full code that finally did the trick (in case anyone else who stumbles upon this in the future has a similar problem):

from pytube import YouTube
import os

yt = YouTube(str(input("Enter the URL of the video you want to download: \n>> ")))

print("Select download format:")
print("1: Video file with audio (.mp4)")
print("2: Audio only (.mp3)")

media_type = input(">> ")

if media_type == "1":
    video = yt.streams.first()

elif media_type == "2":
    video = yt.streams.filter(only_audio = True).first()

else:
    print("Invalid selection.")

print("Enter the destination (leave blank for current directory)")
destination = str(input(">> ")) or '.'

out_file = video.download(output_path = destination)

if media_type == "2":
    base, ext = os.path.splitext(out_file)
    new_file = base + '.mp3'
    os.rename(out_file, new_file)

print(yt.title + " has been successfully downloaded.")

I intend to turn this into a long-term project and extend the script with more features the more I learn, but for now I'm satisfied. Thanks again.

Zareph
  • 43
  • 1
  • 6
  • 1
    What's wrong with your current solution? – gmds Mar 26 '19 at 01:25
  • Added error message now that I finally got one. But in short, the file gets downloaded but it cannot find the file specified and thus the extension doesn't change. – Zareph Mar 26 '19 at 12:02
  • I'm not used to pytube but I'm under the impression that when you select to download just the audio, the library will automatically made it an '.mp3' file. – accdias Mar 26 '19 at 12:21
  • But wait! It just occurred to me that some videos doesn't have an mp3 track for audio. They can use another format. Reading the documentation, I see you can use ```yt.streams.filter(file_extension='mp4')``` to query for the tracks in one specific format to see if there is any available. – accdias Mar 26 '19 at 12:27
  • There are no mp3 tracks on any of the videos I've tested. But either way, I'm curious as to why it says it can't find the file specified when trying to rename it. I tried changing the line with os.rename() in it to include the destination, but I get the same error (this time just including the path I entered, or none if left blank). – Zareph Mar 26 '19 at 16:24

1 Answers1

4

It looks like your code only works correctly when the user input ends with trailing slash.

Use os.path.join() to combine destination directory with filename. Use this expression to default empty to ., the current directory:

destination = str(input(">> ")) or '.'

EDIT:

I was hoping that your assumption, that we could predict the output filespec from the title, was correct. But no. For example, yt = YouTube('https://www.youtube.com/watch?v=9bZkp7q19f0') fetches a PSY music video with a title ending in 'M/V'. Yet .download() will (quite reasonably) construct a filename containing just 'MV', with no slash.

You should not be trying to predict the output filespec. Rather, you should store the return value from .download(), so you will know for certain what the correct filespec is. Here is an example of renaming the output to a constant filename:

>>> out_file = yt.streams.first().download()
>>> os.rename(out_file, 'new.mp3')
>>>

Or if you prefer, you could rename it to os.path.join('/tmp', 'new.mp3').

You might also wish to parse out the extension using splitext:

base, ext = os.path.splitext(out_file)
new_file = base + '.mp3'
os.rename(out_file, new_file)

You may find that the only_audio flag is a convenient way to reduce bandwidth consumed by video, if you prefer to just get the audio tracks:

yt.streams.filter(only_audio=True).all()

EDIT 2021:

You don't have to use os, you can now use:

video.download(output_path=destination, filename=input("Enter file name (without extension): "))
J_H
  • 17,926
  • 4
  • 24
  • 44
  • I think you're right. But I'm trying out your suggestion and clearly I'm doing something wrong. Could you possibly show me how to use `os.path.join()` to accomplish this in my case? I tried defining a function containing `os.path.join(destination, yt.title)` as well as making it into a variable and inserting that into the `os.rename()` function and neither works, but I'm pretty sure my own ineptitude is to blame. – Zareph Mar 26 '19 at 18:50
  • I had to fiddle with it for a while to get this to work, but I finally did. Thank you so much for the help. I've edited the original post with the exact code that got it working for future reference. Cheers! – Zareph Mar 26 '19 at 22:37