Update rutracker categories
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
package indexer
|
||||
|
||||
type Filter interface {
|
||||
IsKnownCategory(categories []string) bool
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func (indexer *JacketIndexer) Search(query string, limit int32, tracker string)
|
||||
}
|
||||
|
||||
url := indexer.cfg.Indexer.Url
|
||||
uri := fmt.Sprintf("%v/api/v2.0/indexers/%v/results/torznab?apikey=%v&limit=%d&q=%v&t=search", url, searchTracker, indexer.cfg.Indexer.ApiKey, limit, query)
|
||||
uri := fmt.Sprintf("%v/api/v2.0/indexers/%v/results/torznab?apikey=%v&limit=%d&cat=3010,3040&q=%v&t=search", url, searchTracker, indexer.cfg.Indexer.ApiKey, limit, query)
|
||||
|
||||
log.Debug().Str("uri", uri).Msg("Sending search request")
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"homelab.lan/music-agregator/internal/tracker/rutracker"
|
||||
)
|
||||
|
||||
var parserFactory = rutracker.NewParserFactory()
|
||||
|
||||
type SearchResult struct {
|
||||
XMLName xml.Name `xml:"rss"`
|
||||
Items []Item `xml:"channel>item"`
|
||||
@@ -37,10 +35,19 @@ type TorznabAttr struct {
|
||||
Value string `xml:"value,attr"`
|
||||
}
|
||||
|
||||
func (sr *SearchResult) ToProto() *pb.SearchResponse {
|
||||
pbItems := make([]*pb.SearchItem, len(sr.Items))
|
||||
var (
|
||||
parserFactory = rutracker.NewParserFactory()
|
||||
filter = rutracker.NewFilter()
|
||||
)
|
||||
|
||||
func (sr *SearchResult) ToProto() *pb.SearchResponse {
|
||||
var pbItems []*pb.SearchItem
|
||||
|
||||
for _, item := range sr.Items {
|
||||
if !filter.IsKnownCategory(item.Categories) {
|
||||
continue
|
||||
}
|
||||
|
||||
for i, item := range sr.Items {
|
||||
pbAttrs := make([]*pb.TorznabAttr, len(item.TorznabAttrs))
|
||||
for j, attr := range item.TorznabAttrs {
|
||||
pbAttrs[j] = &pb.TorznabAttr{
|
||||
@@ -51,7 +58,7 @@ func (sr *SearchResult) ToProto() *pb.SearchResponse {
|
||||
|
||||
release := parserFactory.GetParser(item.Categories).Parse(item.Title)
|
||||
|
||||
pbItems[i] = &pb.SearchItem{
|
||||
pbItems = append(pbItems, &pb.SearchItem{
|
||||
Title: item.Title,
|
||||
DownloadLink: item.Link,
|
||||
TorrentPageUrl: item.Guid,
|
||||
@@ -66,7 +73,7 @@ func (sr *SearchResult) ToProto() *pb.SearchResponse {
|
||||
},
|
||||
TorznabAttrs: pbAttrs,
|
||||
Release: release.ToProto(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return &pb.SearchResponse{
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
package rutracker
|
||||
|
||||
var RockForumIDs = []int{
|
||||
1698, // Зарубежный Rock (parent)
|
||||
1702, // Classic Rock & Hard Rock (lossless)
|
||||
1703, // Classic Rock & Hard Rock (lossy)
|
||||
1704, // Progressive & Art-Rock (lossless)
|
||||
1705, // Progressive & Art-Rock (lossy)
|
||||
1706, // Folk-Rock (lossless)
|
||||
1707, // Folk-Rock (lossy)
|
||||
1708, // Pop-Rock & Soft Rock (lossless)
|
||||
1709, // Pop-Rock & Soft Rock (lossy)
|
||||
1710, // Instrumental Guitar Rock (lossless)
|
||||
1711, // Instrumental Guitar Rock (lossy)
|
||||
1712, // Rockabilly, Psychobilly, Rock'n'Roll (lossless)
|
||||
1713, // Rockabilly, Psychobilly, Rock'n'Roll (lossy)
|
||||
1714, // Восточноазиатский рок (lossless)
|
||||
1715, // Восточноазиатский рок (lossy)
|
||||
722, // Отечественный Rock, Metal (parent)
|
||||
951, // Rock на языках народов xUSSR (lossless)
|
||||
952, // Rock на языках народов xUSSR (lossy)
|
||||
172, // Post-Punk, Shoegaze, Garage Rock, Noise Rock (lossless)
|
||||
236, // Post-Punk, Shoegaze, Garage Rock, Noise Rock (lossy)
|
||||
2175, // Avant-garde, Experimental Rock (lossless)
|
||||
2174, // Avant-garde, Experimental Rock (lossy)
|
||||
2329, // AOR (Melodic Hard Rock, Arena rock) (lossless)
|
||||
2330, // AOR (Melodic Hard Rock, Arena rock) (lossy)
|
||||
731, // Сборники зарубежного рока (lossless)
|
||||
1799, // Сборники зарубежного рока (lossy)
|
||||
737, // Rock (lossless)
|
||||
738, // Rock (lossy)
|
||||
}
|
||||
|
||||
var MetalForumIDs = []int{
|
||||
1716, // Зарубежный Metal (parent)
|
||||
739, // Metal (lossless)
|
||||
740, // Metal (lossy)
|
||||
1719, // Black (lossless)
|
||||
1778, // Black (lossy)
|
||||
1720, // Folk, Pagan, Viking (lossless)
|
||||
798, // Folk, Pagan, Viking (lossy)
|
||||
1724, // Gothic Metal (lossless)
|
||||
1725, // Gothic Metal (lossy)
|
||||
1726, // Heavy, Power, Progressive (lossless)
|
||||
1727, // Heavy, Power, Progressive (lossy)
|
||||
1728, // Thrash, Speed (lossless)
|
||||
1729, // Thrash, Speed (lossy)
|
||||
1730, // Grind, Brutal Death (lossless)
|
||||
1731, // Grind, Brutal Death (lossy)
|
||||
1779, // Death, Doom (lossless)
|
||||
1780, // Death, Doom (lossy)
|
||||
1796, // Avant-garde, Experimental Metal (lossless)
|
||||
1797, // Avant-garde, Experimental Metal (lossy)
|
||||
1815, // Sludge, Stoner, Post-Metal (lossless)
|
||||
1816, // Sludge, Stoner, Post-Metal (lossy)
|
||||
1766, // Зарубежный и Отечественный Metal (оцифровки)
|
||||
}
|
||||
|
||||
var AlternativeForumIDs = []int{
|
||||
1732, // Зарубежные Alternative, Punk, Independent (parent)
|
||||
464, // Alternative, Punk, Independent (lossless)
|
||||
463, // Alternative, Punk, Independent (lossy)
|
||||
123, // Alternative, Punk, Independent (оцифровки)
|
||||
1736, // Alternative & Nu-metal (lossless)
|
||||
1737, // Alternative & Nu-metal (lossy)
|
||||
1738, // Punk (lossless)
|
||||
1739, // Punk (lossy)
|
||||
1740, // Hardcore (lossless)
|
||||
1741, // Hardcore (lossy)
|
||||
1742, // Post-Rock (lossless)
|
||||
1743, // Post-Rock (lossy)
|
||||
1744, // Industrial & Post-industrial (lossless)
|
||||
1745, // Industrial & Post-industrial (lossy)
|
||||
1746, // Emocore, Post-hardcore, Metalcore (lossless)
|
||||
1747, // Emocore, Post-hardcore, Metalcore (lossy)
|
||||
1748, // Gothic Rock & Dark Folk (lossless)
|
||||
1749, // Gothic Rock & Dark Folk (lossy)
|
||||
1773, // Indie Rock, Indie Pop, Dream Pop, Brit-Pop (lossless)
|
||||
202, // Indie Rock, Indie Pop, Dream Pop, Brit-Pop (lossy)
|
||||
466, // Synthwave, Spacesynth, Dreamwave, Retrowave, Outrun (lossless)
|
||||
465, // Synthwave, Spacesynth, Dreamwave, Retrowave, Outrun (lossy)
|
||||
}
|
||||
|
||||
var PopForumIDs = []int{
|
||||
2495, // Отечественная поп-музыка (parent)
|
||||
2497, // Зарубежная поп-музыка (parent)
|
||||
425, // Популярная музыка России и стран бывшего СССР (lossless)
|
||||
424, // Популярная музыка России и стран бывшего СССР (lossy)
|
||||
429, // Зарубежная поп-музыка (lossless)
|
||||
428, // Зарубежная поп-музыка (lossy)
|
||||
1753, // Итальянская поп-музыка (lossless)
|
||||
735, // Итальянская поп-музыка (lossy)
|
||||
714, // Латиноамериканская поп-музыка (lossless)
|
||||
2232, // Латиноамериканская поп-музыка (lossy)
|
||||
1330, // Восточноазиатская поп-музыка (lossless)
|
||||
1331, // Восточноазиатская поп-музыка (lossy)
|
||||
1634, // Советская эстрада, ретро, романсы (lossless)
|
||||
1635, // Советская эстрада, ретро, романсы (lossy)
|
||||
1361, // Популярная музыка России и стран бывшего СССР (сборники) (lossy)
|
||||
1362, // Зарубежная поп-музыка (сборники) (lossy)
|
||||
2270, // Easy Listening, Instrumental Pop (lossless)
|
||||
2275, // Easy Listening, Instrumental Pop (lossy)
|
||||
}
|
||||
|
||||
var ElectronicForumIDs = []int{
|
||||
1807, // House, Techno, Hardcore, Hardstyle, Jumpstyle (parent)
|
||||
1808, // Drum & Bass, Jungle, Breakbeat, Dubstep, IDM, Electro (parent)
|
||||
1809, // Chillout, Lounge, Downtempo, Trip-Hop (parent)
|
||||
1810, // Traditional Electronic, Ambient, Modern Classical, Electroacoustic, Experimental (parent)
|
||||
1811, // Industrial, Noise, EBM, Dark Electro, Aggrotech, Cyberpunk, Synthpop, New Wave (parent)
|
||||
1821, // Trance, Goa Trance, Psy-Trance, PsyChill, Ambient, Dub (parent)
|
||||
2499, // Eurodance, Disco, Hi-NRG (parent)
|
||||
797, // Electro, Electro-Freestyle, Nu Electro (lossless)
|
||||
1805, // Electro, Electro-Freestyle, Nu Electro (lossy)
|
||||
1857, // House (lossless)
|
||||
1858, // House (lossy)
|
||||
1860, // House (Singles, EPs) (lossy)
|
||||
840, // House (Проморелизы, сборники) (lossy)
|
||||
1825, // Techno (lossless)
|
||||
1826, // Techno (lossy)
|
||||
1828, // Techno (Singles, EPs) (lossy)
|
||||
1829, // Hardcore, Hardstyle, Jumpstyle (lossless)
|
||||
1830, // Hardcore, Hardstyle, Jumpstyle (lossy)
|
||||
1831, // Hardcore, Hardstyle, Jumpstyle (vinyl, web)
|
||||
1832, // Drum & Bass, Jungle (lossless)
|
||||
1833, // Drum & Bass, Jungle (lossy)
|
||||
1836, // Breakbeat (lossless)
|
||||
1837, // Breakbeat (lossy)
|
||||
1839, // Dubstep (lossless)
|
||||
454, // Dubstep (lossy)
|
||||
1840, // IDM (lossless)
|
||||
1841, // IDM (lossy)
|
||||
2229, // IDM Discography & Collections (lossy)
|
||||
1818, // Trance (lossless)
|
||||
1819, // Trance (lossy)
|
||||
1847, // Trance (Singles, EPs) (lossy)
|
||||
1844, // Goa Trance, Psy-Trance (lossless)
|
||||
1822, // Goa Trance, Psy-Trance (lossy)
|
||||
1894, // PsyChill, Ambient, Dub (lossless)
|
||||
1895, // PsyChill, Ambient, Dub (lossy)
|
||||
1861, // Chillout, Lounge, Downtempo (lossless)
|
||||
1862, // Chillout, Lounge, Downtempo (lossy)
|
||||
1945, // Trip Hop, Abstract Hip-Hop (lossless)
|
||||
1944, // Trip Hop, Abstract Hip-Hop (lossy)
|
||||
1864, // Traditional Electronic, Ambient (lossless)
|
||||
1865, // Traditional Electronic, Ambient (lossy)
|
||||
1871, // Modern Classical, Electroacoustic (lossless)
|
||||
1867, // Modern Classical, Electroacoustic (lossy)
|
||||
1869, // Experimental (lossless)
|
||||
1873, // Experimental (lossy)
|
||||
1866, // Darkwave, Neoclassical, Ethereal, Dungeon Synth (lossless)
|
||||
406, // Darkwave, Neoclassical, Ethereal, Dungeon Synth (lossy)
|
||||
1868, // EBM, Dark Electro, Aggrotech (lossless)
|
||||
1875, // EBM, Dark Electro, Aggrotech (lossy)
|
||||
1877, // Industrial, Noise (lossless)
|
||||
1878, // Industrial, Noise (lossy)
|
||||
1880, // Synthpop, Futurepop, New Wave, Electropop (lossless)
|
||||
1881, // Synthpop, Futurepop, New Wave, Electropop (lossy)
|
||||
1907, // Cyberpunk, 8-bit, Chiptune (lossy & lossless)
|
||||
2500, // Disco, Italo-Disco, Euro-Disco, Hi-NRG (lossless)
|
||||
2501, // Disco, Italo-Disco, Euro-Disco, Hi-NRG (lossy)
|
||||
2502, // Eurodance, Euro-House, Technopop (lossless)
|
||||
2503, // Eurodance, Euro-House, Technopop (lossy)
|
||||
2504, // Eurodance, Euro-House, Technopop (сборники) (lossy)
|
||||
2505, // Disco, Italo-Disco, Euro-Disco, Hi-NRG (сборники) (lossy)
|
||||
}
|
||||
|
||||
var HipHopForumIDs = []int{
|
||||
408, // Рэп, Хип-Хоп, R'n'B (parent)
|
||||
441, // Отечественный Рэп, Хип-Хоп (lossy)
|
||||
1486, // Отечественный Рэп, Хип-Хоп, R'n'B (lossless)
|
||||
446, // Зарубежный Рэп, Хип-Хоп (lossy)
|
||||
909, // Зарубежный Рэп, Хип-Хоп (lossless)
|
||||
1665, // Зарубежный R'n'B (lossless)
|
||||
1172, // Зарубежный R'n'B (lossy)
|
||||
1173, // Отечественный R'n'B (lossy)
|
||||
2283, // Funk, Soul, R&B (lossless)
|
||||
}
|
||||
|
||||
var JazzForumIDs = []int{
|
||||
2267, // Зарубежный джаз (parent)
|
||||
2269, // Отечественный джаз и блюз (parent)
|
||||
2277, // Early Jazz, Swing, Gypsy (lossless)
|
||||
2278, // Bop (lossless)
|
||||
2279, // Mainstream Jazz, Cool (lossless)
|
||||
2280, // Jazz Fusion (lossless)
|
||||
2281, // World Fusion, Ethnic Jazz (lossless)
|
||||
2282, // Avant-Garde Jazz, Free Improvisation (lossless)
|
||||
2284, // Smooth, Jazz-Pop (lossless)
|
||||
2285, // Vocal Jazz (lossless)
|
||||
2286, // Сборники зарубежного джаза (lossless)
|
||||
2287, // Зарубежный джаз (lossy)
|
||||
2353, // Modern Creative, Third Stream (lossless)
|
||||
1947, // Nu Jazz, Acid Jazz, Future Jazz (lossless)
|
||||
1946, // Nu Jazz, Acid Jazz, Future Jazz (lossy)
|
||||
2297, // Отечественный джаз (lossless)
|
||||
2295, // Отечественный джаз (lossy)
|
||||
}
|
||||
|
||||
var BluesForumIDs = []int{
|
||||
2268, // Зарубежный блюз (parent)
|
||||
2290, // Roots, Pre-War Blues, Early R&B, Gospel (lossless)
|
||||
2292, // Blues-rock (lossless)
|
||||
2293, // Blues (Texas, Chicago, Modern and Others) (lossless)
|
||||
2288, // Зарубежный блюз (lossy)
|
||||
2289, // Зарубежный блюз (сборники; Tribute VA) (lossless)
|
||||
2296, // Отечественный блюз (lossless)
|
||||
2298, // Отечественный блюз (lossy)
|
||||
}
|
||||
|
||||
var ClassicalForumIDs = []int{
|
||||
409, // Классическая и современная академическая музыка (parent)
|
||||
556, // Вокальная музыка (lossless)
|
||||
557, // Оркестровая музыка (lossless)
|
||||
558, // Камерная инструментальная музыка (lossless)
|
||||
793, // Сольная инструментальная музыка (lossless)
|
||||
794, // Опера (lossless)
|
||||
560, // Полные собрания сочинений и многодисковые издания (lossless)
|
||||
436, // Полные собрания сочинений и многодисковые издания (lossy)
|
||||
2307, // Хоровая музыка (lossless)
|
||||
2308, // Концерт для инструмента с оркестром (lossless)
|
||||
2309, // Вокальная и хоровая музыка (lossy)
|
||||
2310, // Оркестровая музыка (lossy)
|
||||
2311, // Камерная и сольная инструментальная музыка (lossy)
|
||||
969, // Классика в современной обработке, Classical Crossover (lossy и lossless)
|
||||
}
|
||||
|
||||
var FolkForumIDs = []int{
|
||||
1125, // Фольклор, Народная и Этническая музыка (parent)
|
||||
1127, // New Age & Meditative (lossless)
|
||||
1126, // New Age & Meditative (lossy)
|
||||
1129, // Этническая музыка Сибири, Средней и Восточной Азии (lossless)
|
||||
1128, // Этническая музыка Сибири, Средней и Восточной Азии (lossy)
|
||||
1131, // Восточноевропейский фолк (lossless)
|
||||
1130, // Восточноевропейский фолк (lossy)
|
||||
1133, // Западноевропейский фолк (lossless)
|
||||
1132, // Западноевропейский фолк (lossy)
|
||||
1135, // Фламенко и акустическая гитара (lossless)
|
||||
1134, // Фламенко и акустическая гитара (lossy)
|
||||
1137, // Country, Bluegrass (lossless)
|
||||
1136, // Country, Bluegrass (lossy)
|
||||
1138, // Этническая музыка Австралии, Тихого и Индийского океанов (lossy и lossless)
|
||||
1282, // Фольклорная, Народная, Эстрадная музыка Кавказа и Закавказья (lossy и lossless)
|
||||
2085, // Этническая музыка Африки и Ближнего Востока (lossless)
|
||||
1283, // Этническая музыка Африки и Ближнего Востока (lossy)
|
||||
1285, // Этническая музыка Северной и Южной Америки (lossless)
|
||||
1284, // Этническая музыка Северной и Южной Америки (lossy)
|
||||
1856, // Этническая музыка Индии (lossy)
|
||||
2430, // Этническая музыка Индии (lossless)
|
||||
2084, // Klezmer и Еврейский фольклор (lossy и lossless)
|
||||
}
|
||||
|
||||
var ReggaeForumIDs = []int{
|
||||
1760, // Reggae, Ska, Dub (parent)
|
||||
1764, // Rocksteady, Early Reggae, Ska-Jazz, Trad.Ska (lossy и lossless)
|
||||
1765, // Reggae (lossy)
|
||||
1768, // Reggae, Dancehall, Dub (lossless)
|
||||
1769, // Ska-Punk, Ska-Core (lossy)
|
||||
1770, // Dancehall, Raggamuffin (lossy)
|
||||
1771, // Dub (lossy)
|
||||
1772, // Отечественный Reggae, Ska, Dub (lossy и lossless)
|
||||
1774, // Ska, Ska-Punk, Ska-Jazz (lossless)
|
||||
1767, // 3rd Wave Ska (lossy)
|
||||
2233, // Reggae, Ska, Dub (компиляции) (lossy и lossless)
|
||||
}
|
||||
|
||||
var SoundtrackForumIDs = []int{
|
||||
416, // Саундтреки, караоке и мюзиклы (parent)
|
||||
691, // Саундтреки к отечественным фильмам (lossless)
|
||||
469, // Саундтреки к отечественным фильмам (lossy)
|
||||
786, // Саундтреки к зарубежным фильмам (lossless)
|
||||
785, // Саундтреки к зарубежным фильмам (lossy)
|
||||
784, // Саундтреки к играм (lossless)
|
||||
783, // Саундтреки к играм (lossy)
|
||||
715, // Саундтреки к мультфильмам (lossy и lossless)
|
||||
1631, // Саундтреки к сериалам (lossless)
|
||||
1499, // Саундтреки к сериалам (lossy)
|
||||
1388, // Саундтреки к аниме (lossless)
|
||||
282, // Саундтреки к аниме (lossy)
|
||||
796, // Неофициальные саундтреки к фильмам и сериалам (lossy)
|
||||
2331, // Неофициальные саундтреки к играм (lossy)
|
||||
2431, // Аранжировки музыки из игр (lossy и lossless)
|
||||
880, // Мюзикл (lossy и lossless)
|
||||
}
|
||||
|
||||
var ShansonForumIDs = []int{
|
||||
1215, // Шансон, Авторская и Военная песня (parent)
|
||||
1220, // Отечественный шансон (lossless)
|
||||
1221, // Отечественный шансон (lossy)
|
||||
1452, // Зарубежный шансон (lossless)
|
||||
1219, // Зарубежный шансон (lossy)
|
||||
1216, // Военная песня, марши (lossless)
|
||||
1223, // Военная песня, марши (lossy)
|
||||
1224, // Авторская песня (lossless)
|
||||
1225, // Авторская песня (lossy)
|
||||
1226, // Менестрели и ролевики (lossy и lossless)
|
||||
1334, // Сборники отечественного шансона (lossy)
|
||||
}
|
||||
|
||||
var HiResForumIDs = []int{
|
||||
1299, // Hi-Res stereo и многоканальная музыка (parent)
|
||||
1755, // Рок-музыка (Hi-Res stereo)
|
||||
1757, // Рок-музыка (многоканальная музыка)
|
||||
1884, // Классика и классика в современной обработке (Hi-Res stereo)
|
||||
1164, // Классика и классика в современной обработке (многоканальная музыка)
|
||||
1885, // Поп-музыка (Hi-Res stereo)
|
||||
1163, // Поп-музыка (многоканальная музыка)
|
||||
1893, // Электронная музыка (Hi-Res stereo)
|
||||
1890, // Электронная музыка (многоканальная музыка)
|
||||
2302, // Джаз и Блюз (Hi-Res stereo)
|
||||
2303, // Джаз и Блюз (многоканальная музыка)
|
||||
1397, // Саундтреки (Hi-Res stereo и многоканальная музыка)
|
||||
2512, // Музыка разных жанров (Hi-Res stereo и многоканальная музыка)
|
||||
2513, // New Age, Relax, Meditative & Flamenco (Hi-Res stereo и многоканальная музыка)
|
||||
1170, // Конверсии SACD
|
||||
453, // Конверсии Quadraphonic
|
||||
1759, // Конверсии Blu-Ray, ADVD и DVD-Audio
|
||||
1852, // Апмиксы-Upmixes
|
||||
860, // Неофициальные конверсии цифровых форматов (parent)
|
||||
}
|
||||
|
||||
var DigitizationForumIDs = []int{
|
||||
2219, // Оцифровки с аналоговых носителей (parent)
|
||||
239, // Отечественная поп-музыка (оцифровки)
|
||||
1444, // Зарубежная поп-музыка (оцифровки)
|
||||
450, // Инструментальная поп-музыка (оцифровки)
|
||||
1756, // Зарубежная рок-музыка (оцифровки)
|
||||
1758, // Отечественная рок-музыка (оцифровки)
|
||||
1754, // Электронная музыка (оцифровки)
|
||||
1660, // Классика и классика в современной обработке (оцифровки)
|
||||
506, // Фольклор, народная и этническая музыка (оцифровки)
|
||||
1835, // Rap, Hip-Hop, R'n'B, Reggae, Ska, Dub (оцифровки)
|
||||
2301, // Джаз и блюз (оцифровки)
|
||||
1217, // Шансон, авторские, военные песни и марши (оцифровки)
|
||||
1625, // Саундтреки и мюзиклы (оцифровки)
|
||||
2401, // Советская эстрада, ретро, романсы (оцифровки)
|
||||
974, // Музыка других жанров (оцифровки)
|
||||
}
|
||||
|
||||
var LabelPackForumIDs = []int{
|
||||
782, // Лейбл- и сцен-паки (parent)
|
||||
1842, // Лейбл-паки (lossless)
|
||||
1648, // Лейбл-паки, Сцен-паки (lossy)
|
||||
134, // Неофициальные сборники и ремастеринги (lossless)
|
||||
965, // Неофициальные сборники (lossy)
|
||||
577, // AI-Music (lossy и lossless)
|
||||
2230, // Сборники (lossless)
|
||||
2231, // Сборники (lossy)
|
||||
}
|
||||
|
||||
var RadioshowForumIDs = []int{
|
||||
1859, // House (Radioshow, Podcast, Liveset, Mixes)
|
||||
1824, // Trance (Radioshows, Podcasts, Live Sets, Mixes) (lossy)
|
||||
1827, // Techno (Radioshows, Podcasts, Livesets, Mixes)
|
||||
1834, // Drum & Bass, Jungle (Radioshows, Podcasts, Livesets, Mixes)
|
||||
1838, // Breakbeat, Dubstep (Radioshows, Podcasts, Livesets, Mixes)
|
||||
460, // Goa Trance, Psy-Trance, PsyChill, Ambient, Dub (Live Sets, Mixes) (lossy)
|
||||
}
|
||||
|
||||
var AACForumIDs = []int{
|
||||
2240, // Музыка Lossy (AAC-iTunes)
|
||||
2244, // Музыка Lossy (AAC) (Singles, EPs)
|
||||
2248, // Музыка Lossy (AAC)
|
||||
1927, // Музыка lossless (ALAC)
|
||||
}
|
||||
|
||||
var MiscMusicForumIDs = []int{
|
||||
1395, // Духовные песнопения и музыка (lossless)
|
||||
1396, // Духовные песнопения и музыка (lossy)
|
||||
1351, // Сборники песен для детей (lossy и lossless)
|
||||
2018, // Музыка для бальных танцев (lossy и lossless)
|
||||
855, // Звуки природы
|
||||
1929, // Смешанные стили
|
||||
}
|
||||
|
||||
var AllMusicForumIDs = concat(
|
||||
RockForumIDs,
|
||||
MetalForumIDs,
|
||||
AlternativeForumIDs,
|
||||
PopForumIDs,
|
||||
ElectronicForumIDs,
|
||||
HipHopForumIDs,
|
||||
JazzForumIDs,
|
||||
BluesForumIDs,
|
||||
ClassicalForumIDs,
|
||||
FolkForumIDs,
|
||||
ReggaeForumIDs,
|
||||
SoundtrackForumIDs,
|
||||
ShansonForumIDs,
|
||||
HiResForumIDs,
|
||||
DigitizationForumIDs,
|
||||
LabelPackForumIDs,
|
||||
RadioshowForumIDs,
|
||||
AACForumIDs,
|
||||
MiscMusicForumIDs,
|
||||
)
|
||||
|
||||
func concat(slices ...[]int) []int {
|
||||
var result []int
|
||||
for _, s := range slices {
|
||||
result = append(result, s...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -11,16 +11,25 @@ type parserType int
|
||||
|
||||
const (
|
||||
parserGeneral parserType = iota
|
||||
parserLossless
|
||||
parserLossy
|
||||
parserHiRes
|
||||
parserVinylDigitization
|
||||
parserClassical
|
||||
parserJazz
|
||||
parserRock
|
||||
parserMetal
|
||||
parserAlternative
|
||||
parserPop
|
||||
parserElectronic
|
||||
parserHipHop
|
||||
parserJazz
|
||||
parserBlues
|
||||
parserClassical
|
||||
parserFolk
|
||||
parserReggae
|
||||
parserSoundtracks
|
||||
parserDiscography
|
||||
parserShanson
|
||||
parserHiRes
|
||||
parserDigitization
|
||||
parserLabelPacks
|
||||
parserRadioshow
|
||||
parserAAC
|
||||
parserMiscMusic
|
||||
)
|
||||
|
||||
var categoryToParser map[int]parserType
|
||||
@@ -28,80 +37,35 @@ var categoryToParser map[int]parserType
|
||||
func init() {
|
||||
categoryToParser = make(map[int]parserType)
|
||||
|
||||
torznabCategories := map[int]parserType{
|
||||
3000: parserGeneral,
|
||||
3010: parserLossy,
|
||||
3040: parserLossless,
|
||||
}
|
||||
categoryToParser[3000] = parserGeneral
|
||||
categoryToParser[3010] = parserGeneral
|
||||
categoryToParser[3040] = parserGeneral
|
||||
|
||||
losslessForumIDs := []int{
|
||||
425, 429, 1760, 1635, 1634, 2495, 1299, 1141, 1660, 1662, 1661, 1852, 1648,
|
||||
1851, 1850, 1633, 1632, 1643, 1846, 2219, 2220, 2221, 1647, 1847, 1848, 1653,
|
||||
738, 739, 740, 1656, 1654, 1655, 1843, 1841, 1842, 408, 1844, 1845, 1849,
|
||||
1650, 1651, 1652, 1659, 1657, 1658, 445, 1664, 1665, 1666, 1669, 1667, 1668,
|
||||
1906, 1907, 1908, 1911, 1909, 1910,
|
||||
}
|
||||
registerAll(RockForumIDs, parserRock)
|
||||
registerAll(MetalForumIDs, parserMetal)
|
||||
registerAll(AlternativeForumIDs, parserAlternative)
|
||||
registerAll(PopForumIDs, parserPop)
|
||||
registerAll(ElectronicForumIDs, parserElectronic)
|
||||
registerAll(HipHopForumIDs, parserHipHop)
|
||||
registerAll(JazzForumIDs, parserJazz)
|
||||
registerAll(BluesForumIDs, parserBlues)
|
||||
registerAll(ClassicalForumIDs, parserClassical)
|
||||
registerAll(FolkForumIDs, parserFolk)
|
||||
registerAll(ReggaeForumIDs, parserReggae)
|
||||
registerAll(SoundtrackForumIDs, parserSoundtracks)
|
||||
registerAll(ShansonForumIDs, parserShanson)
|
||||
registerAll(HiResForumIDs, parserHiRes)
|
||||
registerAll(DigitizationForumIDs, parserDigitization)
|
||||
registerAll(LabelPackForumIDs, parserLabelPacks)
|
||||
registerAll(RadioshowForumIDs, parserRadioshow)
|
||||
registerAll(AACForumIDs, parserAAC)
|
||||
registerAll(MiscMusicForumIDs, parserMiscMusic)
|
||||
}
|
||||
|
||||
lossyForumIDs := []int{
|
||||
424, 428, 1754, 1755, 1756, 1757, 1758, 1759, 1760, 1761, 441, 446,
|
||||
1765, 1766, 1767, 1768, 1769, 1770, 1771,
|
||||
}
|
||||
|
||||
hiResForumIDs := []int{
|
||||
1801, 1807, 1808, 1809, 1810, 1811, 1812, 1813, 1814, 1815, 1816, 1817,
|
||||
2378, 2379, 2380, 2381, 2382, 2383, 2384,
|
||||
}
|
||||
|
||||
vinylForumIDs := []int{
|
||||
1802, 1803, 1804, 1805, 1806,
|
||||
}
|
||||
|
||||
classicalForumIDs := []int{
|
||||
436, 969, 1990, 984, 1125, 1126, 1127, 1128, 1129, 1130, 1131, 1132,
|
||||
1670, 1671, 1672, 1673, 1674, 1675, 1676, 1677,
|
||||
}
|
||||
|
||||
jazzForumIDs := []int{
|
||||
1698, 1699, 1700, 1701, 1702, 1703, 1704, 1705, 1706, 1707, 1708, 1709,
|
||||
}
|
||||
|
||||
metalForumIDs := []int{
|
||||
731, 732, 733, 734, 735, 736, 737, 738, 739, 740,
|
||||
1730, 1731, 1732, 1733, 1734, 1735, 1736, 1737, 1738, 1739,
|
||||
}
|
||||
|
||||
soundtrackForumIDs := []int{
|
||||
691, 702, 704, 705, 706, 707, 708, 709, 710, 711,
|
||||
1631, 469, 786,
|
||||
}
|
||||
|
||||
for id, pt := range torznabCategories {
|
||||
func registerAll(ids []int, pt parserType) {
|
||||
for _, id := range ids {
|
||||
categoryToParser[id] = pt
|
||||
}
|
||||
for _, id := range losslessForumIDs {
|
||||
categoryToParser[id] = parserLossless
|
||||
}
|
||||
for _, id := range lossyForumIDs {
|
||||
categoryToParser[id] = parserLossy
|
||||
}
|
||||
for _, id := range hiResForumIDs {
|
||||
categoryToParser[id] = parserHiRes
|
||||
}
|
||||
for _, id := range vinylForumIDs {
|
||||
categoryToParser[id] = parserVinylDigitization
|
||||
}
|
||||
for _, id := range classicalForumIDs {
|
||||
categoryToParser[id] = parserClassical
|
||||
}
|
||||
for _, id := range jazzForumIDs {
|
||||
categoryToParser[id] = parserJazz
|
||||
}
|
||||
for _, id := range metalForumIDs {
|
||||
categoryToParser[id] = parserMetal
|
||||
}
|
||||
for _, id := range soundtrackForumIDs {
|
||||
categoryToParser[id] = parserSoundtracks
|
||||
}
|
||||
}
|
||||
|
||||
type ParserFactory struct {
|
||||
@@ -112,26 +76,40 @@ func NewParserFactory() *ParserFactory {
|
||||
return &ParserFactory{
|
||||
parsers: map[parserType]tracker.Parser{
|
||||
parserGeneral: parser.NewGeneralParser(),
|
||||
parserLossless: parser.NewLosslessParser(),
|
||||
parserLossy: parser.NewLossyParser(),
|
||||
parserHiRes: parser.NewHiResParser(),
|
||||
parserVinylDigitization: parser.NewVinylDigitizationParser(),
|
||||
parserClassical: parser.NewClassicalParser(),
|
||||
parserJazz: parser.NewJazzParser(),
|
||||
parserRock: parser.NewRockParser(),
|
||||
parserMetal: parser.NewMetalParser(),
|
||||
parserAlternative: parser.NewAlternativeParser(),
|
||||
parserPop: parser.NewPopParser(),
|
||||
parserElectronic: parser.NewElectronicParser(),
|
||||
parserHipHop: parser.NewHipHopParser(),
|
||||
parserJazz: parser.NewJazzParser(),
|
||||
parserBlues: parser.NewBluesParser(),
|
||||
parserClassical: parser.NewClassicalParser(),
|
||||
parserFolk: parser.NewFolkParser(),
|
||||
parserReggae: parser.NewReggaeParser(),
|
||||
parserSoundtracks: parser.NewSoundtracksParser(),
|
||||
parserDiscography: parser.NewDiscographyParser(),
|
||||
parserShanson: parser.NewShansonParser(),
|
||||
parserHiRes: parser.NewHiResParser(),
|
||||
parserDigitization: parser.NewVinylDigitizationParser(),
|
||||
parserLabelPacks: parser.NewLabelPacksParser(),
|
||||
parserRadioshow: parser.NewRadioshowParser(),
|
||||
parserAAC: parser.NewAACParser(),
|
||||
parserMiscMusic: parser.NewMiscMusicParser(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const jackettIDOffset = 100000
|
||||
|
||||
func (f *ParserFactory) GetParser(categories []string) tracker.Parser {
|
||||
for _, cat := range categories {
|
||||
catID, err := strconv.Atoi(cat)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if catID >= jackettIDOffset {
|
||||
catID -= jackettIDOffset
|
||||
}
|
||||
if pt, ok := categoryToParser[catID]; ok {
|
||||
return f.parsers[pt]
|
||||
}
|
||||
|
||||
@@ -15,20 +15,33 @@ func TestParserFactory_GetParser(t *testing.T) {
|
||||
categories []string
|
||||
wantType string
|
||||
}{
|
||||
{"torznab lossless", []string{"3040"}, "*parser.LosslessParser"},
|
||||
{"torznab lossy", []string{"3010"}, "*parser.LossyParser"},
|
||||
{"torznab general", []string{"3000"}, "*parser.GeneralParser"},
|
||||
{"rutracker lossless forum", []string{"425"}, "*parser.LosslessParser"},
|
||||
{"rutracker lossy forum", []string{"424"}, "*parser.LossyParser"},
|
||||
{"rutracker hires forum", []string{"1801"}, "*parser.HiResParser"},
|
||||
{"rutracker vinyl forum", []string{"1802"}, "*parser.VinylDigitizationParser"},
|
||||
{"rutracker classical forum", []string{"436"}, "*parser.ClassicalParser"},
|
||||
{"rutracker jazz forum", []string{"1698"}, "*parser.JazzParser"},
|
||||
{"rutracker metal forum", []string{"731"}, "*parser.MetalParser"},
|
||||
{"rutracker soundtrack forum", []string{"691"}, "*parser.SoundtracksParser"},
|
||||
{"torznab general 3000", []string{"3000"}, "*parser.GeneralParser"},
|
||||
{"torznab general 3010", []string{"3010"}, "*parser.GeneralParser"},
|
||||
{"torznab general 3040", []string{"3040"}, "*parser.GeneralParser"},
|
||||
{"rock forum", []string{"1702"}, "*parser.RockParser"},
|
||||
{"metal forum raw id", []string{"1728"}, "*parser.MetalParser"},
|
||||
{"metal forum jackett id", []string{"101728"}, "*parser.MetalParser"},
|
||||
{"alternative forum", []string{"464"}, "*parser.AlternativeParser"},
|
||||
{"pop forum", []string{"425"}, "*parser.PopParser"},
|
||||
{"electronic forum", []string{"1857"}, "*parser.ElectronicParser"},
|
||||
{"hiphop forum", []string{"909"}, "*parser.HipHopParser"},
|
||||
{"jazz forum", []string{"2277"}, "*parser.JazzParser"},
|
||||
{"blues forum", []string{"2292"}, "*parser.BluesParser"},
|
||||
{"classical forum", []string{"556"}, "*parser.ClassicalParser"},
|
||||
{"folk forum", []string{"1127"}, "*parser.FolkParser"},
|
||||
{"reggae forum", []string{"1768"}, "*parser.ReggaeParser"},
|
||||
{"soundtrack forum", []string{"786"}, "*parser.SoundtracksParser"},
|
||||
{"shanson forum", []string{"1220"}, "*parser.ShansonParser"},
|
||||
{"hires forum", []string{"1755"}, "*parser.HiResParser"},
|
||||
{"digitization forum", []string{"239"}, "*parser.VinylDigitizationParser"},
|
||||
{"label pack forum", []string{"1842"}, "*parser.LabelPacksParser"},
|
||||
{"radioshow forum", []string{"1859"}, "*parser.RadioshowParser"},
|
||||
{"aac forum", []string{"2240"}, "*parser.AACParser"},
|
||||
{"misc music forum", []string{"1395"}, "*parser.MiscMusicParser"},
|
||||
{"unknown category falls back to general", []string{"99999"}, "*parser.GeneralParser"},
|
||||
{"empty categories falls back to general", []string{}, "*parser.GeneralParser"},
|
||||
{"multiple categories uses first match", []string{"99999", "3040"}, "*parser.LosslessParser"},
|
||||
{"multiple categories uses first match", []string{"99999", "1728"}, "*parser.MetalParser"},
|
||||
{"jackett prefixed id stripped", []string{"101719"}, "*parser.MetalParser"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -66,6 +79,30 @@ func getParserTypeName(p tracker.Parser) string {
|
||||
return "*parser.DiscographyParser"
|
||||
case *parser.LabelPacksParser:
|
||||
return "*parser.LabelPacksParser"
|
||||
case *parser.RockParser:
|
||||
return "*parser.RockParser"
|
||||
case *parser.AlternativeParser:
|
||||
return "*parser.AlternativeParser"
|
||||
case *parser.PopParser:
|
||||
return "*parser.PopParser"
|
||||
case *parser.ElectronicParser:
|
||||
return "*parser.ElectronicParser"
|
||||
case *parser.HipHopParser:
|
||||
return "*parser.HipHopParser"
|
||||
case *parser.BluesParser:
|
||||
return "*parser.BluesParser"
|
||||
case *parser.FolkParser:
|
||||
return "*parser.FolkParser"
|
||||
case *parser.ReggaeParser:
|
||||
return "*parser.ReggaeParser"
|
||||
case *parser.ShansonParser:
|
||||
return "*parser.ShansonParser"
|
||||
case *parser.RadioshowParser:
|
||||
return "*parser.RadioshowParser"
|
||||
case *parser.AACParser:
|
||||
return "*parser.AACParser"
|
||||
case *parser.MiscMusicParser:
|
||||
return "*parser.MiscMusicParser"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package rutracker
|
||||
|
||||
import "strconv"
|
||||
|
||||
type Filter struct{}
|
||||
|
||||
func NewFilter() *Filter {
|
||||
return &Filter{}
|
||||
}
|
||||
|
||||
func (f *Filter) IsKnownCategory(categories []string) bool {
|
||||
for _, cat := range categories {
|
||||
catID, err := strconv.Atoi(cat)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if catID >= jackettIDOffset {
|
||||
catID -= jackettIDOffset
|
||||
}
|
||||
if _, ok := categoryToParser[catID]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package rutracker
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFilter_IsKnownCategory(t *testing.T) {
|
||||
f := NewFilter()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
categories []string
|
||||
want bool
|
||||
}{
|
||||
{"torznab lossless", []string{"3040"}, true},
|
||||
{"torznab lossy", []string{"3010"}, true},
|
||||
{"torznab general audio", []string{"3000"}, true},
|
||||
{"rutracker pop forum", []string{"425"}, true},
|
||||
{"rutracker hires forum", []string{"1755"}, true},
|
||||
{"rutracker metal forum", []string{"1728"}, true},
|
||||
{"jackett prefixed id", []string{"101728"}, true},
|
||||
{"unknown category", []string{"99999"}, false},
|
||||
{"empty categories", []string{}, false},
|
||||
{"books category", []string{"7000"}, false},
|
||||
{"mixed known and unknown", []string{"99999", "3040"}, true},
|
||||
{"invalid non-numeric", []string{"abc"}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := f.IsKnownCategory(tt.categories)
|
||||
if got != tt.want {
|
||||
t.Errorf("IsKnownCategory(%v) = %v, want %v", tt.categories, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type AACParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewAACParser() *AACParser {
|
||||
return &AACParser{}
|
||||
}
|
||||
|
||||
func (p *AACParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
}
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Bitrate = p.ExtractBitrate(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.ReleaseCount = p.ExtractReleaseCount(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestAACParser(t *testing.T) {
|
||||
p := NewAACParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Pop AAC VBR",
|
||||
title: "(Pop) Zivert - Айсберг (Apple Music Home Session) - 2022, AAC (tracks), VBR 256 kbps",
|
||||
wantArtist: "Zivert",
|
||||
wantYear: 2022,
|
||||
wantFormat: release.FormatAAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "OST ALAC CD",
|
||||
title: "(OST) [CD] Rockstar Games Presents Music From And Inspired By Grand Theft Auto IV: Vladivostok FM - 2008, ALAC (tracks+.cue), lossless",
|
||||
wantArtist: "Rockstar Games Presents Music From And Inspired By Grand Theft Auto IV: Vladivostok FM",
|
||||
wantYear: 2008,
|
||||
wantFormat: release.FormatALAC,
|
||||
wantType: release.TypeSoundtrack,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Hip-hop ALAC discography",
|
||||
title: "(Hip-Hop, rap rock, hardcore rap, chopper) [CD`39|WEB`6] [Strange Music] Tech N9ne - Дискография / Discography - 1999-2025, ALAC (tracks+.cue), lossless",
|
||||
wantArtist: "Tech N9ne",
|
||||
wantYear: 1999,
|
||||
wantFormat: release.FormatALAC,
|
||||
wantType: release.TypeDiscography,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Art rock iTunes AAC",
|
||||
title: "(Art Rock / Pop Rock) Roxy Music - Дискография / iTunes Discography - 1972-2004 [WEB], AAC (tracks), 256 kbps",
|
||||
wantArtist: "Roxy Music",
|
||||
wantYear: 1972,
|
||||
wantFormat: release.FormatAAC,
|
||||
wantType: release.TypeDiscography,
|
||||
wantBitrate: "256 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Jazz AAC 320",
|
||||
title: "(Jazz, Post-Bop, Modal Music) Masaru Imada + Kenji Kohsei Quartet - All Of A Glow (Hiroshi Murakami, Kenji Kosei, Masaru Imada, Nobuyoshi Ino) - 1978, AAC (tracks), 320 kbps",
|
||||
wantArtist: "Masaru Imada + Kenji Kohsei Quartet",
|
||||
wantYear: 1978,
|
||||
wantFormat: release.FormatAAC,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Rock pop ALAC digital master",
|
||||
title: "(Rock Pop) [WEB] Bryan Adams - Ultimate [Apple Music Digital Master] {24-44.1} - 2017, ALAC (tracks), lossless",
|
||||
wantArtist: "Bryan Adams",
|
||||
wantYear: 2017,
|
||||
wantFormat: release.FormatALAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Alternative electronic VA AAC",
|
||||
title: "(Alternative, Electronic) VA - Astralwerks - Music In 20/20 (Feat. The Chemical Brothers, Doves, Swedish House Mafia, Air, Diamond Rings, Hot Chip, Kings Of Convenience, The Kooks, Kraftwerk & more) - 2013, AAC (tracks), TVBR q127",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2013,
|
||||
wantFormat: release.FormatAAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Eurodance ALAC multi-CD",
|
||||
title: "(EuroHouse, EuroDance, Other) [CD] VA - Promotion Dance Hits (Snake's Music) (22 CD), 1994-1996, ALAC, (tracks+.cue), lossless [не flac]",
|
||||
wantArtist: "VA",
|
||||
wantYear: 1994,
|
||||
wantFormat: release.FormatALAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Lounge chill jazz christmas AAC",
|
||||
title: "(Lounge, Chill Out, Jazz) VA - Christmas Jazz Night 1-7 (Best X-Mas Jazz Music) - 2017-2023, AAC (tracks), TVBR q127 (WEB)",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2017,
|
||||
wantFormat: release.FormatAAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Electro house dance AAC",
|
||||
title: "(Electro, House, Dance) VA - Music & Fashion (The Deep-House Shows), Vol. 1-4 - 2023, AAC (tracks), TVBR q127 (WEB)",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2023,
|
||||
wantFormat: release.FormatAAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := p.Parse(tt.title)
|
||||
|
||||
if r.ParsedSuccessfully != tt.wantParseOK {
|
||||
t.Errorf("ParsedSuccessfully = %v, want %v, errors: %v", r.ParsedSuccessfully, tt.wantParseOK, r.ParseErrors)
|
||||
}
|
||||
|
||||
if tt.wantArtist != "" && r.Artist != tt.wantArtist {
|
||||
t.Errorf("Artist = %q, want %q", r.Artist, tt.wantArtist)
|
||||
}
|
||||
|
||||
if tt.wantYear != 0 && r.Year != tt.wantYear {
|
||||
t.Errorf("Year = %d, want %d", r.Year, tt.wantYear)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type AlternativeParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewAlternativeParser() *AlternativeParser {
|
||||
return &AlternativeParser{}
|
||||
}
|
||||
|
||||
func (p *AlternativeParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
r.Genres = []string{"Alternative"}
|
||||
}
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Bitrate = p.ExtractBitrate(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.ReleaseCount = p.ExtractReleaseCount(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestAlternativeParser(t *testing.T) {
|
||||
p := NewAlternativeParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Nu-metal album",
|
||||
title: "(Nu-Metal, Alternative Metal) [WEB] Korn - Reward the Scars - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Korn",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Punk rock discography",
|
||||
title: "(Punk Rock / Alternative Rock) [CD / WEB] Bayside - Дискография - 2001-2025, (21 CD), FLAC (tracks+cue, tracks), lossless",
|
||||
wantArtist: "Bayside",
|
||||
wantYear: 2001,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Alternative metal discography",
|
||||
title: "(Alternative Metal / Nu Metal) [CD / WEB] Sevendust - Дискография - 1997-2026, (35 CD), FLAC (tracks+cue, tracks), lossless",
|
||||
wantArtist: "Sevendust",
|
||||
wantYear: 1997,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Alt rock female vocals",
|
||||
title: "(Alt. Rock / Alternative Metal / Female Vocals) [WEB] EarlyRise - The Flood Is Coming - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "EarlyRise",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Alternative rock electronic",
|
||||
title: "(Alternative Rock / Post-Hardcore / Electronic) [WEB] Nvtures Ghost - I Have No Moth And I Must Scream - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Nvtures Ghost",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Alternative discography",
|
||||
title: "(Alternative) [WEB] KSB muzic - Дискография - 2022-2025, FLAC (tracks), lossless",
|
||||
wantArtist: "KSB muzic",
|
||||
wantYear: 2022,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Russian indie discography",
|
||||
title: "(Russian Indie, Indie, Rock, Punk, Alternative,) [WEB] Полматери -Дискография (15 релизов) - 2019-2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Полматери",
|
||||
wantYear: 2019,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Britpop discography",
|
||||
title: "(Britpop / Alternative Rock / Indie Rock) [CD / WEB] elbow - Дискография - 2001-2025, (51 CD), FLAC (tracks+cue, tracks), lossless",
|
||||
wantArtist: "elbow",
|
||||
wantYear: 2001,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Alternative rock CD",
|
||||
title: "(Alternative Rock) [CD] Foo Fighters - Your Favorite Toy - 2026, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Foo Fighters",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Smashing Pumpkins multi-genre",
|
||||
title: "(Alternative Rock, Shoegaze, Noise Rock, Dream Pop, Alternative Metal) [CD] The Smashing Pumpkins - Machina II: The Friends & Enemies Of Modern Music (Q101) - 2000 (2 CD), FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "The Smashing Pumpkins",
|
||||
wantYear: 2000,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := p.Parse(tt.title)
|
||||
|
||||
if r.ParsedSuccessfully != tt.wantParseOK {
|
||||
t.Errorf("ParsedSuccessfully = %v, want %v, errors: %v", r.ParsedSuccessfully, tt.wantParseOK, r.ParseErrors)
|
||||
}
|
||||
|
||||
if tt.wantArtist != "" && r.Artist != tt.wantArtist {
|
||||
t.Errorf("Artist = %q, want %q", r.Artist, tt.wantArtist)
|
||||
}
|
||||
|
||||
if tt.wantYear != 0 && r.Year != tt.wantYear {
|
||||
t.Errorf("Year = %d, want %d", r.Year, tt.wantYear)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type BluesParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewBluesParser() *BluesParser {
|
||||
return &BluesParser{}
|
||||
}
|
||||
|
||||
func (p *BluesParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
r.Genres = []string{"Blues"}
|
||||
}
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Bitrate = p.ExtractBitrate(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.ReleaseCount = p.ExtractReleaseCount(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestBluesParser(t *testing.T) {
|
||||
p := NewBluesParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Blues rock classic rock reissue",
|
||||
title: "(Blues Rock, Classic Rock) [CD] Rory Gallagher - Against the Grain - 2018 (1975), FLAC (image+.cue), lossless",
|
||||
wantArtist: "Rory Gallagher",
|
||||
wantYear: 2018,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Blues album WEB",
|
||||
title: "(Blues) [WEB] Roger C. Wade & The Houserockers - Shake it loose! - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Roger C. Wade & The Houserockers",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Blues rock soldier",
|
||||
title: "(Blues Rock) [WEB] Krissy Matthews - Rock and Roll Soldier - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Krissy Matthews",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Blues folk album",
|
||||
title: "(Blues, Folk) [WEB] Gurf Morlix - Cobwebs & Stardust - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Gurf Morlix",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Blues dan penn",
|
||||
title: "(Blues) [WEB] Dan Penn - Smoke Filled Room - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Dan Penn",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Blues rock paradise",
|
||||
title: "(Blues Rock) [WEB] Catfish John Tisdell - Blues in Paradise - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Catfish John Tisdell",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Blues shades",
|
||||
title: "(Blues) [WEB] Carrie Marshall - Shades of Blue - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Carrie Marshall",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Blues rock dont be mean",
|
||||
title: "(Blues Rock) [WEB] Boogie Beasts - Don't Be So Mean! - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Boogie Beasts",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Blues against machine",
|
||||
title: "(Blues) [WEB] Blues Against The Machine - VOL. II - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Blues Against The Machine",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Blues bon appetit",
|
||||
title: "(Blues) [WEB] Andhrea and the Black Cats - Bon Appetit!! - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Andhrea and the Black Cats",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := p.Parse(tt.title)
|
||||
|
||||
if r.ParsedSuccessfully != tt.wantParseOK {
|
||||
t.Errorf("ParsedSuccessfully = %v, want %v, errors: %v", r.ParsedSuccessfully, tt.wantParseOK, r.ParseErrors)
|
||||
}
|
||||
|
||||
if tt.wantArtist != "" && r.Artist != tt.wantArtist {
|
||||
t.Errorf("Artist = %q, want %q", r.Artist, tt.wantArtist)
|
||||
}
|
||||
|
||||
if tt.wantYear != 0 && r.Year != tt.wantYear {
|
||||
t.Errorf("Year = %d, want %d", r.Year, tt.wantYear)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type ElectronicParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewElectronicParser() *ElectronicParser {
|
||||
return &ElectronicParser{}
|
||||
}
|
||||
|
||||
func (p *ElectronicParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
r.Genres = []string{"Electronic"}
|
||||
}
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Bitrate = p.ExtractBitrate(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.ReleaseCount = p.ExtractReleaseCount(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestElectronicParser(t *testing.T) {
|
||||
p := NewElectronicParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Progressive house VA",
|
||||
title: "(Progressive House) [WEB] VA - Augmented 018 / FGA (Mango Alley [ALLEYAUG018]) - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Electro synth-pop tech house",
|
||||
title: "(Electro, Synth-Pop, Tech House) [CD] VA - Kitsune Maison Compilation 6 - 2008, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2008,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Deep house multi-CD",
|
||||
title: "(Deep House, House, Tech House, Minimal Techno) [2 CD] VA - Freza & Nitrous - Air Trip - 2012, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2012,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "House fresh majestic",
|
||||
title: "(House) [2 CD] VA - Fresh & Majestic - defile spb [2005] - 2005, FLAC (image+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2005,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Trance breaks house",
|
||||
title: "(Trance, Breaks, House) [2 CD] VA - Fantazia - Aural Pleasure - 2000, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2000,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "House klubnyi",
|
||||
title: "(House) [CD] VA - E Burg KLUBНЫЙ by Smart #5 - 2006, FLAC (image+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2006,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "House progressive artist release",
|
||||
title: "(House, Progressive house) [WEB] Thomas Newson - Summer Vibes (Armada Music[ARMAS1092A]) - 2015, FLAC (tracks), lossless",
|
||||
wantArtist: "Thomas Newson",
|
||||
wantYear: 2015,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Progressive trance dream",
|
||||
title: "(Progressive Trance, Euro House, Trance, Dream) [CD] VA - Dream Power 7 - 1997, FLAC (image+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 1997,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Progressive house hard house",
|
||||
title: "(Progressive House, Hard House) [CD] VA - Future Russian House - 2001, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2001,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Progressive house trance 2002",
|
||||
title: "(Progressive House, Trance) [CD] VA - Future Russian House 2002 - 2002, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2002,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := p.Parse(tt.title)
|
||||
|
||||
if r.ParsedSuccessfully != tt.wantParseOK {
|
||||
t.Errorf("ParsedSuccessfully = %v, want %v, errors: %v", r.ParsedSuccessfully, tt.wantParseOK, r.ParseErrors)
|
||||
}
|
||||
|
||||
if tt.wantArtist != "" && r.Artist != tt.wantArtist {
|
||||
t.Errorf("Artist = %q, want %q", r.Artist, tt.wantArtist)
|
||||
}
|
||||
|
||||
if tt.wantYear != 0 && r.Year != tt.wantYear {
|
||||
t.Errorf("Year = %d, want %d", r.Year, tt.wantYear)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type FolkParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewFolkParser() *FolkParser {
|
||||
return &FolkParser{}
|
||||
}
|
||||
|
||||
func (p *FolkParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
r.Genres = []string{"Folk"}
|
||||
}
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Bitrate = p.ExtractBitrate(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.ReleaseCount = p.ExtractReleaseCount(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestFolkParser(t *testing.T) {
|
||||
p := NewFolkParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Folk italo VA compilation",
|
||||
title: "(Folk, Italo Folk, Italian Folk) [WEB] VA - La musica della mafia - Best of (Uomini d'onore - Men of Honor) - 2011, FLAC (tracks), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2011,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Russian folk VA",
|
||||
title: "[RUS](Folk) [CD] VA - Пинежская песня. Том I-III, V, VI - 2011-2016, FLAC (image+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2011,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Folk world country best of",
|
||||
title: "(Folk, World, & Country) [CD] Suzy Bogguss - Greatest Hits - 1994, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Suzy Bogguss",
|
||||
wantYear: 1994,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Folk christmas multi-CD",
|
||||
title: "(Folk) [CD] The Allisons - Sing Christmas (2 CD) - 1995, FLAC (image+.cue), lossless",
|
||||
wantArtist: "The Allisons",
|
||||
wantYear: 1995,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Folk world country album",
|
||||
title: "(Folk, World, & Country) [CD] Maura O'Connell - Helpless Heart - 1989, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Maura O'Connell",
|
||||
wantYear: 1989,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Country blues folk collection",
|
||||
title: "(Country, Blues, Folk, Americana, Guitar, Vocal) [WEB] Jack Barksdale - Collection of 2 Albums, 2 EP and 8 singles / Коллекция из 12 релизов - 2017-2023, FLAC (tracks), lossless",
|
||||
wantArtist: "Jack Barksdale",
|
||||
wantYear: 2017,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCollection,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Russian dark folk neofolk collection",
|
||||
title: "[RUS] (Folk, Dark Folk, Neofolk) [CD] Помни Имя Своё - Коллекция (4 релиза, 6 CD) - 2016-2023, FLAC (image+.cue), lossless",
|
||||
wantArtist: "Помни Имя Своё",
|
||||
wantYear: 2016,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Folk country rock collection",
|
||||
title: "(Folk, Country rock) [CD] Emmylou Harris - коллекция 1975-2008 (23 альбома), FLAC (image+.cue, tracks+.cue), lossless",
|
||||
wantArtist: "Emmylou Harris",
|
||||
wantYear: 1975,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Ukrainian folk electronic collection",
|
||||
title: "[UKR] (Folk, Electronic, Dance, Pop) [WEB] Go A - Collection - 2016-2026, FLAC (tracks), 17 CD (1 Album, 16 Singles), lossless",
|
||||
wantArtist: "Go A",
|
||||
wantYear: 2016,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCollection,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Alternative country folk rock",
|
||||
title: "(Alternative Country, Folk Rock) [CD] Kathleen Edwards - Asking for Flowers - 2008, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Kathleen Edwards",
|
||||
wantYear: 2008,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := p.Parse(tt.title)
|
||||
|
||||
if r.ParsedSuccessfully != tt.wantParseOK {
|
||||
t.Errorf("ParsedSuccessfully = %v, want %v, errors: %v", r.ParsedSuccessfully, tt.wantParseOK, r.ParseErrors)
|
||||
}
|
||||
|
||||
if tt.wantArtist != "" && r.Artist != tt.wantArtist {
|
||||
t.Errorf("Artist = %q, want %q", r.Artist, tt.wantArtist)
|
||||
}
|
||||
|
||||
if tt.wantYear != 0 && r.Year != tt.wantYear {
|
||||
t.Errorf("Year = %d, want %d", r.Year, tt.wantYear)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type HipHopParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewHipHopParser() *HipHopParser {
|
||||
return &HipHopParser{}
|
||||
}
|
||||
|
||||
func (p *HipHopParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
r.Genres = []string{"Hip-Hop"}
|
||||
}
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Bitrate = p.ExtractBitrate(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.ReleaseCount = p.ExtractReleaseCount(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestHipHopParser(t *testing.T) {
|
||||
p := NewHipHopParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Rap underground discography",
|
||||
title: "(Rap | Underground Hip-Hop) Небро | Честер / Chester (Небро) - Дискография, (при уч. НЕ.KURILI) , (39 Релизов), - 2008-2026, MP3, 256-320 kbps",
|
||||
wantArtist: "Небро | Честер / Chester (Небро)",
|
||||
wantYear: 2008,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Rap album MP3",
|
||||
title: "(Rap) Честер Небро & НЕ.KURILI - Короткометражка - 2026, MP3, 320 kbps",
|
||||
wantArtist: "Честер Небро & НЕ.KURILI",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Hip-hop Kanye West",
|
||||
title: "(Hip-Hop, Rap, Electronic) [CD][LDR] Ye (Kanye West) - Bully - 2026, FLAC (image+.cue), lossless",
|
||||
wantArtist: "Ye (Kanye West)",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Rap Russian album",
|
||||
title: "(Rap) MC Кальмар & Алкоголь После Спорта - Антибиотик - 2025, MP3, 320 kbps",
|
||||
wantArtist: "MC Кальмар & Алкоголь После Спорта",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Rap hip-hop discography complex",
|
||||
title: "(Rap/Hip-Hop) Killer Chem [Razym Garo] (при участии: AnderМаг, 03406(И.С), 2DT, Бандарад Вирши) - Официальная ДискоТрекография (3 альбома, 8 синглов, Трекография) - 2020-2026, MP3, 192-320 Kbps",
|
||||
wantArtist: "Killer Chem [Razym Garo] (при участии: AnderМаг, 03406(И.С), 2DT, Бандарад Вирши)",
|
||||
wantYear: 2020,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Rap kapa album",
|
||||
title: "(Rap) Капа - КАПАкалипсис - 2026, MP3, 320 kbps",
|
||||
wantArtist: "Капа",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Rap grot album",
|
||||
title: "(Rap) Грот - между катастроф - 2026, MP3, 320 kbps",
|
||||
wantArtist: "Грот",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Rap hip-hop collection FLAC",
|
||||
title: "(Rap, Hip-Hop) [CD] [WEB] † Эйсик (Asick) - Коллекция (13 релизов) - 2005-2023, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "† Эйсик (Asick)",
|
||||
wantYear: 2005,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Hip-hop rap WEB",
|
||||
title: "(Hip-Hop/Rap) [WEB] Aarne - AA LANGUAGE (Uncensored) - 2022, FLAC (tracks), lossless",
|
||||
wantArtist: "Aarne",
|
||||
wantYear: 2022,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Rap fast alberto",
|
||||
title: "(Rap) Фаст Альберто - Ars Longa Vita Brevis - 2026, MP3, 320 kbps",
|
||||
wantArtist: "Фаст Альберто",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := p.Parse(tt.title)
|
||||
|
||||
if r.ParsedSuccessfully != tt.wantParseOK {
|
||||
t.Errorf("ParsedSuccessfully = %v, want %v, errors: %v", r.ParsedSuccessfully, tt.wantParseOK, r.ParseErrors)
|
||||
}
|
||||
|
||||
if tt.wantArtist != "" && r.Artist != tt.wantArtist {
|
||||
t.Errorf("Artist = %q, want %q", r.Artist, tt.wantArtist)
|
||||
}
|
||||
|
||||
if tt.wantYear != 0 && r.Year != tt.wantYear {
|
||||
t.Errorf("Year = %d, want %d", r.Year, tt.wantYear)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type MiscMusicParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewMiscMusicParser() *MiscMusicParser {
|
||||
return &MiscMusicParser{}
|
||||
}
|
||||
|
||||
func (p *MiscMusicParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
}
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Bitrate = p.ExtractBitrate(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.ReleaseCount = p.ExtractReleaseCount(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestMiscMusicParser(t *testing.T) {
|
||||
p := NewMiscMusicParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Spiritual chants FLAC",
|
||||
title: "(Духовные песнопения) [CD] Хор Сретенского Монастыря - Рождественские песнопения - 2005, FLAC (image+.cue), lossless",
|
||||
wantArtist: "Хор Сретенского Монастыря",
|
||||
wantYear: 2005,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Children songs VA",
|
||||
title: "(Детские песни) [CD] VA - Любимые песни из мультфильмов - 2010, FLAC (tracks), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2010,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Ballroom latin VA",
|
||||
title: "(Ballroom, Latin) [CD] VA - Dancelife: The Best Of Latin Music - 2008, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2008,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Nature sounds MP3",
|
||||
title: "(Nature Sounds) VA - Sounds Of Nature: Rainforest - 2005, MP3, 320 kbps",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2005,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "320 kbps",
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Spiritual gregorian WEB",
|
||||
title: "(Spiritual) [WEB] Gregorian - Masters of Chant - 2006, FLAC (tracks), lossless",
|
||||
wantArtist: "Gregorian",
|
||||
wantYear: 2006,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Mixed styles VA compilation",
|
||||
title: "(Mixed Styles) VA - Various Artists Compilation 2024 - 2024, MP3, 320 kbps",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2024,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "320 kbps",
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Children lullabies WEB",
|
||||
title: "(Детские песни) [WEB] VA - Колыбельные для малышей - 2020, FLAC (tracks), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2020,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Orthodox chants CD",
|
||||
title: "(Духовная музыка) [CD] VA - Православные песнопения - 2012, FLAC (image+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2012,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Ballroom cha cha cha",
|
||||
title: "(Ballroom) [CD] VA - Strictly Ballroom Dancing - Cha Cha Cha - 2006, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2006,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Nature sounds relaxation",
|
||||
title: "(Nature Sounds, Relaxation) VA - Ocean Waves: Calm & Relax - 2018, MP3, 256 kbps",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2018,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "256 kbps",
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := p.Parse(tt.title)
|
||||
|
||||
if r.ParsedSuccessfully != tt.wantParseOK {
|
||||
t.Errorf("ParsedSuccessfully = %v, want %v, errors: %v", r.ParsedSuccessfully, tt.wantParseOK, r.ParseErrors)
|
||||
}
|
||||
|
||||
if tt.wantArtist != "" && r.Artist != tt.wantArtist {
|
||||
t.Errorf("Artist = %q, want %q", r.Artist, tt.wantArtist)
|
||||
}
|
||||
|
||||
if tt.wantYear != 0 && r.Year != tt.wantYear {
|
||||
t.Errorf("Year = %d, want %d", r.Year, tt.wantYear)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type PopParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewPopParser() *PopParser {
|
||||
return &PopParser{}
|
||||
}
|
||||
|
||||
func (p *PopParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
r.Genres = []string{"Pop"}
|
||||
}
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Bitrate = p.ExtractBitrate(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.ReleaseCount = p.ExtractReleaseCount(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestPopParser(t *testing.T) {
|
||||
p := NewPopParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Pop CD album",
|
||||
title: "(Pop) [CD] Nessa Barrett - AFTERCARE DELUXE - 2025, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Nessa Barrett",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Pop alternative deluxe",
|
||||
title: "(Pop) (Alternative) [CD] Melanie Martinez - Cry Baby (Deluxe Edition) - 2015, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Melanie Martinez",
|
||||
wantYear: 2015,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Pop album",
|
||||
title: "(Pop) [CD] Ava Max - Diamonds & Dancefloors - 2023, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Ava Max",
|
||||
wantYear: 2023,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Pop VA compilation",
|
||||
title: "(Pop) [WEB] VA - Музыка Победы - 2025, FLAC (tracks), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Pop limited edition",
|
||||
title: "(Pop) [CD] Стрелки - Gold [Limited Edition] Maschina Records - 2026, FLAC (image+.cue), lossless",
|
||||
wantArtist: "Стрелки",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Pop Europop",
|
||||
title: "(Pop, Europop) [CD] Pupo - Insieme (2025), FLAC (image+.cue), lossless",
|
||||
wantArtist: "Pupo",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Pop VA multi-CD",
|
||||
title: "(Pop) [CD] VA - The Best Of 1980-1990 Vol. II [3 CD] - 1990, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 1990,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Pop dont click play",
|
||||
title: "(Pop) [CD] Ava Max - Don't Click Play - 2025, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Ava Max",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Pop no good",
|
||||
title: "(Pop) [CD] Ivy Levan - No Good - 2015, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Ivy Levan",
|
||||
wantYear: 2015,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Pop vocal soul",
|
||||
title: "(Pop, Vocal, Soul, R&B) [WEB] Hush Dusty - Love is a Battlefield Tonight - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Hush Dusty",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := p.Parse(tt.title)
|
||||
|
||||
if r.ParsedSuccessfully != tt.wantParseOK {
|
||||
t.Errorf("ParsedSuccessfully = %v, want %v, errors: %v", r.ParsedSuccessfully, tt.wantParseOK, r.ParseErrors)
|
||||
}
|
||||
|
||||
if tt.wantArtist != "" && r.Artist != tt.wantArtist {
|
||||
t.Errorf("Artist = %q, want %q", r.Artist, tt.wantArtist)
|
||||
}
|
||||
|
||||
if tt.wantYear != 0 && r.Year != tt.wantYear {
|
||||
t.Errorf("Year = %d, want %d", r.Year, tt.wantYear)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type RadioshowParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewRadioshowParser() *RadioshowParser {
|
||||
return &RadioshowParser{}
|
||||
}
|
||||
|
||||
func (p *RadioshowParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
r.Genres = []string{"Electronic"}
|
||||
}
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Bitrate = p.ExtractBitrate(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.ReleaseCount = p.ExtractReleaseCount(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestRadioshowParser(t *testing.T) {
|
||||
p := NewRadioshowParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "BBC Essential Mix AAC",
|
||||
title: "(House, Progressive House, Tech House, Dance, Electro, DnB) BBC Radio One - Essential Mix 2026, AAC (tracks), 320 kbps",
|
||||
wantArtist: "BBC Radio One",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatAAC,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Russian mega mix MP3",
|
||||
title: "(Club House, Progressive House, Russian Pop) Alex Kerdivar - Russian Mega Mix 21 (26.04.2026), MP3, 320 kbps",
|
||||
wantArtist: "Alex Kerdivar",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Drum and bass fussy listener",
|
||||
title: "(Intelligent Drum & Bass) LTJ Bukem - Fussy Listener Mix Vol 3 - 11.02.2026, MP3, 192 kbps",
|
||||
wantArtist: "LTJ Bukem",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "192 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Neurofunk BBC Radio",
|
||||
title: "(Neurofunk) Enta - Production Showcase Mix (BBC Radio 1) - 17.11.2024, MP3, 320 kbps",
|
||||
wantArtist: "Enta",
|
||||
wantYear: 2024,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Dark techstep methlab radio",
|
||||
title: "(Drum & Bass, Dark Techstep) Allied - MethLab Radio Guest Mix [MLR040] - 05.11.2015, MP3, 320 kbps",
|
||||
wantArtist: "Allied",
|
||||
wantYear: 2015,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Goldie 6 mix VBR",
|
||||
title: "(Drum & Bass) Goldie - The 6 Mix (BBC Radio 6) - 06-06-2025, MP3, V0",
|
||||
wantArtist: "Goldie",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "V0",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Daphni Essential Mix",
|
||||
title: "(House, Tech House) Daphni - BBC Radio 1s Essential Mix - 17-01-2026, MP3, V0",
|
||||
wantArtist: "Daphni",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "V0",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Andy C 6 mix",
|
||||
title: "(Drum & Bass) Andy C - The 6 Mix (BBC Radio 6) 16-01-2026, MP3, V0",
|
||||
wantArtist: "Andy C",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "V0",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Club house Russian rap mix",
|
||||
title: "(Club House, Russian Rap, Rap, Hip-Hop) Alex Kerdivar - Special Mega Mix 14 (17.01.2026), MP3, 320 kbps",
|
||||
wantArtist: "Alex Kerdivar",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Jungle phonica mix series",
|
||||
title: "(Drum & Bass, Jungle) Tim Reaper - Phonica Mix Series 128 (DJ Mix) - 2025, MP3, 320 kbps",
|
||||
wantArtist: "Tim Reaper",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := p.Parse(tt.title)
|
||||
|
||||
if r.ParsedSuccessfully != tt.wantParseOK {
|
||||
t.Errorf("ParsedSuccessfully = %v, want %v, errors: %v", r.ParsedSuccessfully, tt.wantParseOK, r.ParseErrors)
|
||||
}
|
||||
|
||||
if tt.wantArtist != "" && r.Artist != tt.wantArtist {
|
||||
t.Errorf("Artist = %q, want %q", r.Artist, tt.wantArtist)
|
||||
}
|
||||
|
||||
if tt.wantYear != 0 && r.Year != tt.wantYear {
|
||||
t.Errorf("Year = %d, want %d", r.Year, tt.wantYear)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type ReggaeParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewReggaeParser() *ReggaeParser {
|
||||
return &ReggaeParser{}
|
||||
}
|
||||
|
||||
func (p *ReggaeParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
r.Genres = []string{"Reggae"}
|
||||
}
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Bitrate = p.ExtractBitrate(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.ReleaseCount = p.ExtractReleaseCount(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestReggaeParser(t *testing.T) {
|
||||
p := NewReggaeParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Reggae funk soul album",
|
||||
title: "(Reggae, Funk / Soul) [CD] Diana King - Think Like A Girl (CD Album, Enhanced) - 1997, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Diana King",
|
||||
wantYear: 1997,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Reggae dawn penn",
|
||||
title: "(Reggae) [CD] Dawn Penn - Come Again [1996] - 1996, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Dawn Penn",
|
||||
wantYear: 1996,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Reggae ska Bob Marley",
|
||||
title: "(Reggae, Ska) [CD] Bob Marley & The Wailers - 3 альбома - (1973-1980), FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Bob Marley & The Wailers",
|
||||
wantYear: 1973,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Reggae Bob Marley collection",
|
||||
title: "(Reggae) [CD] Bob Marley & The Wailers - коллекция 1970-2012 (86 альбомов), FLAC (image+.cue, tracks+.cue), lossless",
|
||||
wantArtist: "Bob Marley & The Wailers",
|
||||
wantYear: 1970,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Reggae rock ska",
|
||||
title: "(Reggae Rock, Ska) [CD] The English Beat - Special Beat Service - 1986, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "The English Beat",
|
||||
wantYear: 1986,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Reggae VA celebration",
|
||||
title: "(Reggae, Reggae-Pop, Ragga, Euro-House) [CD] VA - Reggae Celebration '97 Vol. 1 - 1997, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 1997,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Reggae big youth",
|
||||
title: "(Reggae) [CD] Big Youth - Natty Universal Dread 1973-1979 - 2000, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Big Youth",
|
||||
wantYear: 2000,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Reggae barrington levy multi-CD",
|
||||
title: "(Reggae) [CD] Barrington Levy - Sweet Reggae Music 1979-84 (2 CD) - 2012, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Barrington Levy",
|
||||
wantYear: 2012,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Sega-reggae elijah",
|
||||
title: "(Sega-Reggae) [CD] ELIJAH - Luveologist - 2006, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "ELIJAH",
|
||||
wantYear: 2006,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Reggae UB40 ultimate edition",
|
||||
title: "(Reggae) [WEB] UB40 - UB45 [Ultimate Edition] - 2024, FLAC (tracks), lossless",
|
||||
wantArtist: "UB40",
|
||||
wantYear: 2024,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := p.Parse(tt.title)
|
||||
|
||||
if r.ParsedSuccessfully != tt.wantParseOK {
|
||||
t.Errorf("ParsedSuccessfully = %v, want %v, errors: %v", r.ParsedSuccessfully, tt.wantParseOK, r.ParseErrors)
|
||||
}
|
||||
|
||||
if tt.wantArtist != "" && r.Artist != tt.wantArtist {
|
||||
t.Errorf("Artist = %q, want %q", r.Artist, tt.wantArtist)
|
||||
}
|
||||
|
||||
if tt.wantYear != 0 && r.Year != tt.wantYear {
|
||||
t.Errorf("Year = %d, want %d", r.Year, tt.wantYear)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type RockParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewRockParser() *RockParser {
|
||||
return &RockParser{}
|
||||
}
|
||||
|
||||
func (p *RockParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
r.Genres = []string{"Rock"}
|
||||
}
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Bitrate = p.ExtractBitrate(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.ReleaseCount = p.ExtractReleaseCount(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestRockParser(t *testing.T) {
|
||||
p := NewRockParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Blues rock single",
|
||||
title: "(Rock, Blues Rock) [WEB] The Rolling Stones - In The Stars [Single] - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "The Rolling Stones",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeSingle,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Hard rock single",
|
||||
title: "(Hard Rock) [WEB] J.R. Blackmore - Moments Of Magic (Single) - 2012, FLAC (tracks), lossless",
|
||||
wantArtist: "J.R. Blackmore",
|
||||
wantYear: 2012,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeSingle,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Psychedelic rock collection",
|
||||
title: "(Psychedelic Rock, Hard Rock, Blues Rock, Progressive Rock) [CD] Atomic Rooster - Collection Albums 1970-1973 (8 CD), FLAC (image+.cue), lossless",
|
||||
wantArtist: "Atomic Rooster",
|
||||
wantYear: 1970,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCollection,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Rock discography",
|
||||
title: "(Rock) [CD] Voice Of The Beehive - Discography - 1988-2022 (18 releases), FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Voice Of The Beehive",
|
||||
wantYear: 1988,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeDiscography,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Indie rock re-release",
|
||||
title: "(Indie Rock / Indie Pop) [WEB] Easy - Magic Seed - 1990, FLAC (tracks), lossless(Re-release)",
|
||||
wantArtist: "Easy",
|
||||
wantYear: 1990,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Progressive rock album",
|
||||
title: "(Progressive Rock) [WEB] Os Mutantes - De Volta Ao Planeta Dos Mutantes - 2006, FLAC (tracks), lossless",
|
||||
wantArtist: "Os Mutantes",
|
||||
wantYear: 2006,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Hard rock reissue",
|
||||
title: "(Hard Rock) [CD] Gene Simmons - Gene Simmons - 1978 (1991), FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Gene Simmons",
|
||||
wantYear: 1978,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Pop rock collection",
|
||||
title: "(Pop Rock) [CD] Sneakers (with Sanne Salomonsen) - Collection (1980-1997) (4 releases), FLAC (image+.cue), lossless",
|
||||
wantArtist: "Sneakers (with Sanne Salomonsen)",
|
||||
wantYear: 1980,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCollection,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Southern rock album",
|
||||
title: "(Southern Rock, Hard Rock, Blues Rock) [WEB] The Cold Stares - Texas - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "The Cold Stares",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Hard rock 70s style",
|
||||
title: "(Hard Rock, 70's) [WEB] Lynx - Trinity of Suns - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Lynx",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := p.Parse(tt.title)
|
||||
|
||||
if r.ParsedSuccessfully != tt.wantParseOK {
|
||||
t.Errorf("ParsedSuccessfully = %v, want %v, errors: %v", r.ParsedSuccessfully, tt.wantParseOK, r.ParseErrors)
|
||||
}
|
||||
|
||||
if tt.wantArtist != "" && r.Artist != tt.wantArtist {
|
||||
t.Errorf("Artist = %q, want %q", r.Artist, tt.wantArtist)
|
||||
}
|
||||
|
||||
if tt.wantYear != 0 && r.Year != tt.wantYear {
|
||||
t.Errorf("Year = %d, want %d", r.Year, tt.wantYear)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type ShansonParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewShansonParser() *ShansonParser {
|
||||
return &ShansonParser{}
|
||||
}
|
||||
|
||||
func (p *ShansonParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
r.Genres = []string{"Shanson"}
|
||||
}
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Bitrate = p.ExtractBitrate(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.ReleaseCount = p.ExtractReleaseCount(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestShansonParser(t *testing.T) {
|
||||
p := NewShansonParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "French shanson VA multi-CD",
|
||||
title: "(Shanson) [4 CD] VA - Chansons Francaises - 2011, FLAC (image+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2011,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Retro shanson album",
|
||||
title: "(Retro-Shanson) [CD] Биртман - Следы от компота - 2015, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Биртман",
|
||||
wantYear: 2015,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Pop shanson collection",
|
||||
title: "(Pop, Shanson) Sylvie Vartan - Best Artist Collection - 198?, APE (tracks+.cue) lossless",
|
||||
wantArtist: "Sylvie Vartan",
|
||||
wantFormat: release.FormatAPE,
|
||||
wantType: release.TypeCollection,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "French shanson Piaf",
|
||||
title: "(French Shanson) Jil Aigrot - Words Of Love: The Voice of Edith Piaf in the Award-winning Film La Vie En Rose - 2008, APE (image+.cue), lossless",
|
||||
wantArtist: "Jil Aigrot",
|
||||
wantYear: 2008,
|
||||
wantFormat: release.FormatAPE,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Pop shanson Joe Dassin best of",
|
||||
title: "(Pop/Shanson) Joe Dassin - Greatest Hits (2 CDs SET DIGIPACK) - 2007, FLAC (image + .cue), lossless",
|
||||
wantArtist: "Joe Dassin",
|
||||
wantYear: 2007,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Shanson Adamo WavPack",
|
||||
title: "(Shanson) Salvatore Adamo - L'essentiel - 2003, WAVPack (image+.cue), lossless",
|
||||
wantArtist: "Salvatore Adamo",
|
||||
wantYear: 2003,
|
||||
wantFormat: release.FormatWavPack,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Lounge shanson french pop",
|
||||
title: "(Lounge, Shanson, French-Pop) Helena Noguerra - Nee Dans La Nature - 2004, APE (image + .cue), lossless",
|
||||
wantArtist: "Helena Noguerra",
|
||||
wantYear: 2004,
|
||||
wantFormat: release.FormatAPE,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Shanson VA blatnaya",
|
||||
title: "(Shanson) [CD] VA - Блатная Империя 100 лучших Хитов - 2007, FLAC (tracks), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2007,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Bard song collection",
|
||||
title: "(Авторская песня) [CD] Булат Окуджава - Коллекция (20 CD) - 1967-2001, FLAC (image+.cue), lossless",
|
||||
wantArtist: "Булат Окуджава",
|
||||
wantYear: 1967,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Russian shanson discography MP3",
|
||||
title: "(Шансон) Михаил Круг - Дискография (34 альбома) - 1994-2009, MP3, 192-320 kbps",
|
||||
wantArtist: "Михаил Круг",
|
||||
wantYear: 1994,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantParseOK: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := p.Parse(tt.title)
|
||||
|
||||
if r.ParsedSuccessfully != tt.wantParseOK {
|
||||
t.Errorf("ParsedSuccessfully = %v, want %v, errors: %v", r.ParsedSuccessfully, tt.wantParseOK, r.ParseErrors)
|
||||
}
|
||||
|
||||
if tt.wantArtist != "" && r.Artist != tt.wantArtist {
|
||||
t.Errorf("Artist = %q, want %q", r.Artist, tt.wantArtist)
|
||||
}
|
||||
|
||||
if tt.wantYear != 0 && r.Year != tt.wantYear {
|
||||
t.Errorf("Year = %d, want %d", r.Year, tt.wantYear)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user