Skip to content

Commit

Permalink
Opus codec
Browse files Browse the repository at this point in the history
  • Loading branch information
glomatico committed Mar 3, 2023
1 parent 899831d commit e222c4e
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 35 deletions.
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
# Glomatico's YouTube Music Downloader
Download YouTube Music songs/albums/playlists with tags from YouTube Music in 128kbps/256kbps AAC and following the iTunes standard.
Download YouTube Music songs/albums/playlists with tags from YouTube Music in 128kbps AAC/128kbps Opus/256kbps AAC and following the iTunes standard.

## Setup
1. Install Python 3.8 or higher
2. Install gytmdl with pip
```
pip install gytmdl
```
3. Add MP4Box to your PATH. You can get it from here: https://gpac.wp.imt.fr/downloads/
3. Add FFMPEG to your PATH. You can get it from here: https://ffmpeg.org/download.html
* If you are on Windows you can move the `ffmpeg.exe` file to the same folder that you will run the script instead of adding it to your PATH.
4. (optional) Get your cookies.txt
* With cookies.txt, you can download age restricted tracks, private playlists and songs in 256kbps AAC using `--premium-quality` argument if you are a premium user. You can export your cookies by using the following Google Chrome extension on YouTube Music website with your account logged in: https://chrome.google.com/webstore/detail/gdocmgbfkjnnpapoeobnolbbkoibbcif. Make sure to export it as `cookies.txt` to the same folder that you will run the script.
* With cookies.txt, you can download age restricted tracks, private playlists and songs in 256kbps AAC using `--itag 141` argument if you are a premium user. You can export your cookies by using the following Google Chrome extension on YouTube Music website with your account logged in: https://chrome.google.com/webstore/detail/gdocmgbfkjnnpapoeobnolbbkoibbcif. Make sure to export it as `cookies.txt` to the same folder that you will run the script.

## Usage
```
usage: gytmdl [-h] [-u [URLS_TXT]] [-t TEMP_PATH] [-f FINAL_PATH] [-c COOKIES_LOCATION] [-p] [-s] [-e] [-v]
[<url> ...]
usage: gytmdl [-h] [-u [URLS_TXT]] [-t TEMP_PATH] [-f FINAL_PATH] [-c COOKIES_LOCATION] [-i {141,251,140}] [-o]
[-s] [-e] [-v]
[<url> ...]
Download YouTube Music songs/albums/playlists with tags from YouTube Music in 128kbps/256kbps AAC
Download YouTube Music songs/albums/playlists with tags from YouTube Music
positional arguments:
<url> YouTube Music song/album/playlist URL(s) (default: None)
Expand All @@ -31,8 +33,10 @@ options:
Final path (default: YouTube Music)
-c COOKIES_LOCATION, --cookies-location COOKIES_LOCATION
Cookies location (default: cookies.txt)
-p, --premium-quality
Download 256kbps AAC instead of 128kbps AAC (default: False)
-i {141,251,140}, --itag {141,251,140}
itag (quality). Can be 141 (256kbps AAC, requires cookies), 251 (128kbps Opus) or 140 (128kbps
AAC) (default: 140)
-o, --overwrite Overwrite existing files (default: False)
-s, --skip-cleanup Skip cleanup (default: False)
-e, --print-exceptions
Print exceptions (default: False)
Expand Down
42 changes: 26 additions & 16 deletions gytmdl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import traceback
from .gytmdl import Gytmdl

__version__ = '1.0'
__version__ = '1.1'


def main():
if not shutil.which('MP4Box'):
raise Exception('MP4Box is not on PATH.')
if not shutil.which('ffmpeg'):
raise Exception('ffmpeg is not on PATH')
parser = argparse.ArgumentParser(
description = 'Download YouTube Music songs/albums/playlists with tags from YouTube Music in 128kbps/256kbps AAC',
description = 'Download YouTube Music songs/albums/playlists with tags from YouTube Music',
formatter_class = argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument(
Expand Down Expand Up @@ -44,10 +44,17 @@ def main():
help = 'Cookies location'
)
parser.add_argument(
'-p',
'--premium-quality',
'-i',
'--itag',
default = '140',
help = 'itag (quality). Can be 141 (256kbps AAC, requires cookies), 251 (128kbps Opus) or 140 (128kbps AAC)',
choices = ['141', '251', '140']
)
parser.add_argument(
'-o',
'--overwrite',
action = 'store_true',
help = 'Download 256kbps AAC instead of 128kbps AAC'
help = 'Overwrite existing files'
)
parser.add_argument(
'-s',
Expand All @@ -69,10 +76,11 @@ def main():
)
args = parser.parse_args()
dl = Gytmdl(
args.cookies_location,
args.premium_quality,
args.final_path,
args.temp_path,
args.cookies_location,
args.itag,
args.final_path,
args.temp_path,
args.overwrite,
args.skip_cleanup
)
if not args.url and not args.urls_txt:
Expand All @@ -89,30 +97,32 @@ def main():
exit(1)
except:
error_count += 1
print(f'* Failed to check URL {i + 1}.')
print(f'Failed to check URL {i + 1}/{len(args.url)}')
if args.print_exceptions:
traceback.print_exc()
for i, url in enumerate(download_queue):
for j, track in enumerate(url):
print(f'Downloading "{track["title"]}" (track {j + 1} from URL {i + 1})...')
print(f'Downloading "{track["title"]}" (track {j + 1}/{len(url)} from URL {i + 1}/{len(download_queue)})')
try:
ytmusic_watch_playlist = dl.get_ytmusic_watch_playlist(track['id'])
if ytmusic_watch_playlist is None:
track['id'] = dl.search_track(track['title'])
ytmusic_watch_playlist = dl.get_ytmusic_watch_playlist(track['id'])
tags = dl.get_tags(ytmusic_watch_playlist)
final_location = dl.get_final_location(tags)
if final_location.exists() and not args.overwrite:
continue
temp_location = dl.get_temp_location(track['id'])
dl.download(track['id'], temp_location)
fixed_location = dl.get_fixed_location(track['id'])
dl.fixup(temp_location, fixed_location)
final_location = dl.get_final_location(tags)
dl.make_final(final_location, fixed_location, tags)
except KeyboardInterrupt:
exit(1)
except:
error_count += 1
print(f'* Failed to download "{track["title"]}" (track {j + 1} from URL {i + 1}).')
print(f'Failed to download "{track["title"]}" (track {j + 1}/{len(url)}) from URL {i + 1}/{len(download_queue)})')
if args.print_exceptions:
traceback.print_exc()
dl.cleanup()
print(f'Done ({error_count} error(s)).')
print(f'Done ({error_count} error(s))')
33 changes: 22 additions & 11 deletions gytmdl/gytmdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@


class Gytmdl:
def __init__(self, cookies_location, premium_quality, final_path, temp_path, skip_cleanup):
def __init__(self, cookies_location, itag, final_path, temp_path, overwrite, skip_cleanup):
self.ytmusic = YTMusic()
self.cookies_location = Path(cookies_location)
self.itag = '141' if premium_quality else '140'
self.itag = itag
self.final_path = Path(final_path)
self.temp_path = Path(temp_path)
self.overwrite = overwrite
self.skip_cleanup = skip_cleanup


Expand Down Expand Up @@ -117,7 +118,7 @@ def get_sanizated_string(self, dirty_string, is_folder):


def get_temp_location(self, video_id):
return self.temp_path / f'{video_id}.m4a'
return self.temp_path / f'{video_id}.mp4'


def get_fixed_location(self, video_id):
Expand All @@ -132,7 +133,7 @@ def download(self, video_id, temp_location):
ydl_opts = {
'quiet': True,
'no_warnings': True,
'overwrites': True,
'overwrites': self.overwrite,
'fixup': 'never',
'format': self.itag,
'outtmpl': str(temp_location)
Expand All @@ -144,14 +145,23 @@ def download(self, video_id, temp_location):


def fixup(self, temp_location, fixed_location):
fixup = [
'ffmpeg',
'-loglevel',
'error',
'-i',
temp_location
]
if self.itag == '251':
fixup.extend([
'-f',
'mp4'
])
subprocess.run(
[
'MP4Box',
'-quiet',
'-add',
temp_location,
'-itags',
'artist=placeholder',
*fixup,
'-c',
'copy',
fixed_location
],
check = True
Expand All @@ -161,7 +171,8 @@ def fixup(self, temp_location, fixed_location):
def make_final(self, final_location, fixed_location, tags):
final_location.parent.mkdir(parents = True, exist_ok = True)
shutil.copy(fixed_location, final_location)
file = MP4(final_location).tags
file = MP4(final_location)
file.clear()
file.update(tags)
file.save(final_location)

Expand Down

0 comments on commit e222c4e

Please sign in to comment.