mirror of
https://github.com/beetbox/beets.git
synced 2026-01-16 05:02:28 +01:00
Merge branch '1060_ft_lang_support'
This commit is contained in:
commit
c2184be679
4 changed files with 84 additions and 15 deletions
|
|
@ -678,3 +678,17 @@ def max_filename_length(path, limit=MAX_FILENAME_LENGTH):
|
|||
return min(res[9], limit)
|
||||
else:
|
||||
return limit
|
||||
|
||||
|
||||
def feat_tokens(for_artist=True):
|
||||
"""Return a regular expression that matches phrases like "featuring"
|
||||
that separate a main artist or a song title from secondary artists.
|
||||
The `for_artist` option determines whether the regex should be
|
||||
suitable for matching artist fields (the default) or title fields.
|
||||
"""
|
||||
feat_words = ['ft', 'featuring', 'feat', 'feat.', 'ft.']
|
||||
if for_artist:
|
||||
feat_words += ['with', 'vs', 'and', 'con', '&']
|
||||
return '(?<=\s)(?:{0})(?=\s)'.format(
|
||||
'|'.join(re.escape(x) for x in feat_words)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
"""
|
||||
from beets.plugins import BeetsPlugin
|
||||
from beets import ui
|
||||
from beets.util import displayable_path
|
||||
from beets.util import displayable_path, feat_tokens
|
||||
from beets import config
|
||||
import logging
|
||||
import re
|
||||
|
|
@ -30,25 +30,19 @@ def split_on_feat(artist):
|
|||
artist, which is always a string, and the featuring artist, which
|
||||
may be a string or None if none is present.
|
||||
"""
|
||||
parts = re.split(
|
||||
r'[fF]t\.|[fF]eaturing|[fF]eat\.|\b[wW]ith\b|&|vs\.|and',
|
||||
artist,
|
||||
1, # Only split on the first "feat".
|
||||
)
|
||||
parts = [s.strip() for s in parts]
|
||||
# split on the first "feat".
|
||||
regex = re.compile(feat_tokens(), re.IGNORECASE)
|
||||
parts = [s.strip() for s in regex.split(artist, 1)]
|
||||
if len(parts) == 1:
|
||||
return parts[0], None
|
||||
else:
|
||||
return parts
|
||||
return tuple(parts)
|
||||
|
||||
|
||||
def contains_feat(title):
|
||||
"""Determine whether the title contains a "featured" marker.
|
||||
"""
|
||||
return bool(re.search(
|
||||
r'[fF]t\.|[fF]eaturing|[fF]eat\.|\b[wW]ith\b|&',
|
||||
title,
|
||||
))
|
||||
return bool(re.search(feat_tokens(), title, flags=re.IGNORECASE))
|
||||
|
||||
|
||||
def update_metadata(item, feat_part, drop_feat):
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ from HTMLParser import HTMLParseError
|
|||
from beets.plugins import BeetsPlugin
|
||||
from beets import ui
|
||||
from beets import config
|
||||
from beets.util import feat_tokens
|
||||
|
||||
|
||||
# Global logger.
|
||||
|
|
@ -137,7 +138,7 @@ def search_pairs(item):
|
|||
artists = [artist]
|
||||
|
||||
# Remove any featuring artists from the artists name
|
||||
pattern = r"(.*?) (&|\b(and|ft|feat(uring)?\b))"
|
||||
pattern = r"(.*?) {0}".format(feat_tokens())
|
||||
match = re.search(pattern, artist, re.IGNORECASE)
|
||||
if match:
|
||||
artists.append(match.group(1))
|
||||
|
|
@ -150,8 +151,8 @@ def search_pairs(item):
|
|||
titles.append(match.group(1))
|
||||
|
||||
# Remove any featuring artists from the title
|
||||
pattern = r"(.*?) \b(ft|feat(uring)?)\b"
|
||||
for title in titles:
|
||||
pattern = r"(.*?) {0}".format(feat_tokens(for_artist=False))
|
||||
for title in titles[:]:
|
||||
match = re.search(pattern, title, re.IGNORECASE)
|
||||
if match:
|
||||
titles.append(match.group(1))
|
||||
|
|
|
|||
60
test/test_ftintitle.py
Normal file
60
test/test_ftintitle.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# This file is part of beets.
|
||||
# Copyright 2014, Fabrice Laporte.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Tests for the 'ftintitle' plugin."""
|
||||
|
||||
from _common import unittest
|
||||
from beetsplug import ftintitle
|
||||
|
||||
|
||||
class FtInTitlePluginTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up configuration"""
|
||||
ftintitle.FtInTitlePlugin()
|
||||
|
||||
def test_split_on_feat(self):
|
||||
parts = ftintitle.split_on_feat('Alice ft. Bob')
|
||||
self.assertEqual(parts, ('Alice', 'Bob'))
|
||||
parts = ftintitle.split_on_feat('Alice feat Bob')
|
||||
self.assertEqual(parts, ('Alice', 'Bob'))
|
||||
parts = ftintitle.split_on_feat('Alice feat. Bob')
|
||||
self.assertEqual(parts, ('Alice', 'Bob'))
|
||||
parts = ftintitle.split_on_feat('Alice featuring Bob')
|
||||
self.assertEqual(parts, ('Alice', 'Bob'))
|
||||
parts = ftintitle.split_on_feat('Alice & Bob')
|
||||
self.assertEqual(parts, ('Alice', 'Bob'))
|
||||
parts = ftintitle.split_on_feat('Alice and Bob')
|
||||
self.assertEqual(parts, ('Alice', 'Bob'))
|
||||
parts = ftintitle.split_on_feat('Alice With Bob')
|
||||
self.assertEqual(parts, ('Alice', 'Bob'))
|
||||
parts = ftintitle.split_on_feat('Alice defeat Bob')
|
||||
self.assertEqual(parts, ('Alice defeat Bob', None))
|
||||
|
||||
def test_contains_feat(self):
|
||||
self.assertTrue(ftintitle.contains_feat('Alice ft. Bob'))
|
||||
self.assertTrue(ftintitle.contains_feat('Alice feat. Bob'))
|
||||
self.assertTrue(ftintitle.contains_feat('Alice feat Bob'))
|
||||
self.assertTrue(ftintitle.contains_feat('Alice featuring Bob'))
|
||||
self.assertTrue(ftintitle.contains_feat('Alice & Bob'))
|
||||
self.assertTrue(ftintitle.contains_feat('Alice and Bob'))
|
||||
self.assertTrue(ftintitle.contains_feat('Alice With Bob'))
|
||||
self.assertFalse(ftintitle.contains_feat('Alice defeat Bob'))
|
||||
self.assertFalse(ftintitle.contains_feat('Aliceft.Bob'))
|
||||
|
||||
|
||||
def suite():
|
||||
return unittest.TestLoader().loadTestsFromName(__name__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='suite')
|
||||
Loading…
Reference in a new issue