Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
__pycache__
/data
.github/.tmp/
/.vs
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,18 @@ ytimport:
max_likes: 15
set:
loved: true
auth_headers: /path/to/your/http/headers
auth_headers: ''
oauth_client_id: ''
oauth_client_secret: ''
cookiefile: path/to/your/cookies
min_length: 60 # 1m; min track length in seconds
max_length: 7200 # 2h; max track length in seconds
max_length_nochapter: 900 # 15m; max track length when no chapters defined
split_tracks: true
group_albums: true
quiet_fallback: skip # optional; alternatively, to import as is, set 'asis'.
oauth_client_id: ''
oauth_client_secret: ''
Comment on lines +52 to +53

@mgoltzsche mgoltzsche Jul 11, 2025

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add/document the cookiefile config option here.

Also, I think it would be good to keep all the authentication options listed together (next to auth_headers).

Also, would you mind briefly explaining in the README how to obtain the cookies file or link to another page that's explaining that?

(Similarly, we/I should document how to obtain the oauth client credentials but that's not blocking your PR.)

```

For more information, see [CLI](#cli).
Expand All @@ -57,7 +62,10 @@ Once you enabled the `ytimport` plugin within your beets configuration, you can
beet ytimport --likes --max-likes 3
```

Please note that the command prompts you for Google authentication, unless you specified the `auth_headers` option within your beets configuration file pointing to a file containing HTTP headers (to get the HTTP headers, see [here](https://ytmusicapi.readthedocs.io/en/stable/setup/browser.html#copy-authentication-headers)).
Please note that the command prompts you for Google authentication, unless you specified the `auth_headers` option within your beets configuration file pointing to a file containing HTTP headers (to get the HTTP headers, see [here](https://ytmusicapi.readthedocs.io/en/stable/setup/browser.html#copy-authentication-headers)).
For Authentication oauth clientID and Secret are required (this requres a google cloud console account, see [here](https://ytmusicapi.readthedocs.io/en/stable/setup/oauth.html)).
Alternativly you can pass your cookie file to ytimport (to retrieve it use one of many cookie export extensions [eg](https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/)
To imp
Import auto-tagger prompts can be disabled by specifying the `-q` option.
You can interrupt and continue or repeat the command to synchronize likes from your Youtube account(s) into your beets library incrementally.

Expand Down Expand Up @@ -99,6 +107,8 @@ Options:
--quiet-fallback=skip|asis
decision in quiet mode when there is no strong match
--pretend don't import but print the files when importing
--cookiefile path to a file containing the cookies for a logged in
user on music.youtube.com in Netscape format
```

## Development
Expand Down
6 changes: 5 additions & 1 deletion beetsplug/ytimport/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def commands(self):
def run_import_cmd(lib, opts, args):
ytdir = os.path.expanduser(opts.directory)
headers = os.path.expanduser(opts.auth_headers)
cookiefile = os.path.expanduser(opts.cookiefile)
if headers:
with open(headers, 'r') as f:
headers = f.read()
Expand Down Expand Up @@ -95,7 +96,7 @@ def run_import_cmd(lib, opts, args):
# Maybe a cookiefile with some picked cookies from the headers can be generated?
#if opts.auth and headers:
# h = dict([l.split(': ', 1) for l in headers.strip().split('\n')[1:]])
youtube.download(urls, ytdir, format=opts.format, min_len=opts.min_length, max_len=opts.max_length, max_len_nochapter=opts.max_length_nochapter, split=opts.split_tracks, like=opts.likes, reimport=opts.reimport, auth_headers=h)
youtube.download(urls, ytdir, cookiefile, format=opts.format, min_len=opts.min_length, max_len=opts.max_length, max_len_nochapter=opts.max_length_nochapter, split=opts.split_tracks, like=opts.likes, reimport=opts.reimport, auth_headers=h)
else:
self._log.info('Nothing to download')
if opts.do_import:
Expand All @@ -118,6 +119,9 @@ def run_import_cmd(lib, opts, args):
p.add_option('--auth-headers', type='string', metavar='FILE',
default=self.config['auth_headers'].get(),
dest='auth_headers', help="path to a file containing the HTTP headers of an authenticated POST request to music.youtube.com, copied from your browser's development tool")
p.add_option('--cookiefile', type='string', metavar='FILE',
default=self.config['cookiefile'].get(),
dest='cookiefile', help='path to a file containing the cookies for a logged in user on music.youtube.com in Netscape format')
p.add_option('--url-file', type='string', metavar='URL',
default=self.config['url_file'].get(),
dest='url_file', help='URL/path to a file containing a download URL per line')
Expand Down
1 change: 1 addition & 0 deletions beetsplug/ytimport/config_default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ group_albums: true
#quiet_fallback: skip # defaults to import configuration
oauth_client_id: ''
oauth_client_secret: ''
#cookies: '/path/to/your/cookies'
4 changes: 2 additions & 2 deletions beetsplug/ytimport/youtube.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def run(self, info):
os.remove(fname)
return [], info

def download(urls, target_dir, format='bestaudio/best', min_len=60, max_len=7200, max_len_nochapter=900, split=False, like=False, reimport=False, auth_headers={}):
def download(urls, target_dir, cookiefile, format='bestaudio/best', min_len=60, max_len=7200, max_len_nochapter=900, split=False, like=False, reimport=False, auth_headers={}):

def download_filter(info):
duration = info.get('duration')
Expand Down Expand Up @@ -129,7 +129,7 @@ def download_filter(info):
'key': 'XAttrMetadata',
},
],
# TODO: maybe enable for premium quality: 'cookiefile': 'path/to/cookies.txt',
'cookiefile': cookiefile,
}
if not reimport:
ytdl_opts['download_archive'] = target_dir+'/ytdownloads.txt'
Expand Down