Remove rutracker parser, replace with GenericParser for all indexer results
This commit is contained in:
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
pb "homelab.lan/music-agregator/gen/music_agregator/indexer/v1"
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
"homelab.lan/music-agregator/internal/tracker/rutracker"
|
||||
"homelab.lan/music-agregator/internal/tracker"
|
||||
)
|
||||
|
||||
type SearchResult struct {
|
||||
@@ -90,15 +90,13 @@ func (sr *SearchResponse) ToProto() *pb.SearchResponse {
|
||||
return &pb.SearchResponse{Result: pbItems}
|
||||
}
|
||||
|
||||
var (
|
||||
rutrackerParserFactory = rutracker.NewRuTrackerParserFactory()
|
||||
)
|
||||
var genericParser = tracker.NewGenericParser()
|
||||
|
||||
func (sr *SearchResult) ToSearchResponse() *SearchResponse {
|
||||
var items []*SearchItemResult
|
||||
|
||||
for _, item := range sr.Items {
|
||||
rel := rutrackerParserFactory.GetParser(item.Categories).Parse(item.Title)
|
||||
rel := genericParser.Parse(item.Title)
|
||||
|
||||
log.Trace().
|
||||
Str("tracker", item.JackettIndexer.ID).
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package tracker
|
||||
|
||||
type ParserFactory interface {
|
||||
GetParser(categories []string) Parser
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package tracker
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type Parser interface {
|
||||
Parse(title string) *release.Release
|
||||
}
|
||||
@@ -1,404 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package rutracker
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"homelab.lan/music-agregator/internal/tracker"
|
||||
"homelab.lan/music-agregator/internal/tracker/rutracker/parser"
|
||||
)
|
||||
|
||||
type parserType int
|
||||
|
||||
const (
|
||||
parserGeneral parserType = iota
|
||||
parserRock
|
||||
parserMetal
|
||||
parserAlternative
|
||||
parserPop
|
||||
parserElectronic
|
||||
parserHipHop
|
||||
parserJazz
|
||||
parserBlues
|
||||
parserClassical
|
||||
parserFolk
|
||||
parserReggae
|
||||
parserSoundtracks
|
||||
parserShanson
|
||||
parserHiRes
|
||||
parserDigitization
|
||||
parserLabelPacks
|
||||
parserRadioshow
|
||||
parserAAC
|
||||
parserMiscMusic
|
||||
)
|
||||
|
||||
var categoryToParser map[int]parserType
|
||||
|
||||
func init() {
|
||||
categoryToParser = make(map[int]parserType)
|
||||
|
||||
categoryToParser[3000] = parserGeneral
|
||||
categoryToParser[3010] = parserGeneral
|
||||
categoryToParser[3040] = parserGeneral
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func registerAll(ids []int, pt parserType) {
|
||||
for _, id := range ids {
|
||||
categoryToParser[id] = pt
|
||||
}
|
||||
}
|
||||
|
||||
type ParserFactory struct {
|
||||
parsers map[parserType]tracker.Parser
|
||||
}
|
||||
|
||||
func NewRuTrackerParserFactory() *ParserFactory {
|
||||
return &ParserFactory{
|
||||
parsers: map[parserType]tracker.Parser{
|
||||
parserGeneral: parser.NewGeneralParser(),
|
||||
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(),
|
||||
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]
|
||||
}
|
||||
}
|
||||
return f.parsers[parserGeneral]
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package rutracker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/tracker"
|
||||
"homelab.lan/music-agregator/internal/tracker/rutracker/parser"
|
||||
)
|
||||
|
||||
func TestParserFactory_GetParser(t *testing.T) {
|
||||
f := NewRuTrackerParserFactory()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
categories []string
|
||||
wantType string
|
||||
}{
|
||||
{"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", "1728"}, "*parser.MetalParser"},
|
||||
{"jackett prefixed id stripped", []string{"101719"}, "*parser.MetalParser"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := f.GetParser(tt.categories)
|
||||
gotType := getParserTypeName(p)
|
||||
if gotType != tt.wantType {
|
||||
t.Errorf("GetParser(%v) = %v, want %v", tt.categories, gotType, tt.wantType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getParserTypeName(p tracker.Parser) string {
|
||||
switch p.(type) {
|
||||
case *parser.GeneralParser:
|
||||
return "*parser.GeneralParser"
|
||||
case *parser.LosslessParser:
|
||||
return "*parser.LosslessParser"
|
||||
case *parser.LossyParser:
|
||||
return "*parser.LossyParser"
|
||||
case *parser.HiResParser:
|
||||
return "*parser.HiResParser"
|
||||
case *parser.VinylDigitizationParser:
|
||||
return "*parser.VinylDigitizationParser"
|
||||
case *parser.ClassicalParser:
|
||||
return "*parser.ClassicalParser"
|
||||
case *parser.JazzParser:
|
||||
return "*parser.JazzParser"
|
||||
case *parser.MetalParser:
|
||||
return "*parser.MetalParser"
|
||||
case *parser.SoundtracksParser:
|
||||
return "*parser.SoundtracksParser"
|
||||
case *parser.DiscographyParser:
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,282 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
type BaseParser struct{}
|
||||
|
||||
func (p *BaseParser) NewRelease(title string) *release.Release {
|
||||
return &release.Release{
|
||||
RawTitle: title,
|
||||
ParsedSuccessfully: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BaseParser) ExtractGenres(title string) []string {
|
||||
match := genrePattern.FindStringSubmatch(title)
|
||||
if len(match) < 2 {
|
||||
return nil
|
||||
}
|
||||
raw := match[1]
|
||||
parts := strings.FieldsFunc(raw, func(r rune) bool {
|
||||
return r == ',' || r == '/' || r == ';'
|
||||
})
|
||||
var genres []string
|
||||
for _, part := range parts {
|
||||
trimmed := strings.TrimSpace(part)
|
||||
if trimmed != "" {
|
||||
genres = append(genres, trimmed)
|
||||
}
|
||||
}
|
||||
return genres
|
||||
}
|
||||
|
||||
func (p *BaseParser) StripGenrePrefix(title string) string {
|
||||
return genrePattern.ReplaceAllString(title, "")
|
||||
}
|
||||
|
||||
func (p *BaseParser) StripLeadingTags(title string) string {
|
||||
return leadingTagsPattern.ReplaceAllString(title, "")
|
||||
}
|
||||
|
||||
func (p *BaseParser) ExtractYear(title string) int {
|
||||
if match := reissueYearPattern.FindStringSubmatch(title); len(match) >= 2 {
|
||||
year, _ := strconv.Atoi(match[1])
|
||||
return year
|
||||
}
|
||||
|
||||
if match := releaseYearPattern.FindStringSubmatch(title); len(match) >= 2 {
|
||||
year, _ := strconv.Atoi(match[1])
|
||||
return year
|
||||
}
|
||||
|
||||
match := yearPattern.FindStringSubmatch(title)
|
||||
if len(match) < 2 {
|
||||
return 0
|
||||
}
|
||||
year, _ := strconv.Atoi(match[1])
|
||||
return year
|
||||
}
|
||||
|
||||
func (p *BaseParser) ExtractYearRange(title string) (int, int) {
|
||||
if match := releaseYearPattern.FindStringSubmatch(title); len(match) >= 2 {
|
||||
year, _ := strconv.Atoi(match[1])
|
||||
return year, 0
|
||||
}
|
||||
|
||||
if match := reissueYearPattern.FindStringSubmatch(title); len(match) >= 2 {
|
||||
year, _ := strconv.Atoi(match[1])
|
||||
return year, 0
|
||||
}
|
||||
|
||||
rangeMatch := yearRangePattern.FindStringSubmatch(title)
|
||||
if len(rangeMatch) >= 3 {
|
||||
start, _ := strconv.Atoi(rangeMatch[1])
|
||||
end, _ := strconv.Atoi(rangeMatch[2])
|
||||
return start, end
|
||||
}
|
||||
|
||||
match := yearPattern.FindStringSubmatch(title)
|
||||
if len(match) >= 2 {
|
||||
year, _ := strconv.Atoi(match[1])
|
||||
return year, 0
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (p *BaseParser) ExtractFormat(title string) release.AudioFormat {
|
||||
match := formatPattern.FindStringSubmatch(title)
|
||||
if len(match) < 2 {
|
||||
return release.FormatUnknown
|
||||
}
|
||||
format := strings.ToUpper(match[1])
|
||||
switch {
|
||||
case format == "FLAC":
|
||||
return release.FormatFLAC
|
||||
case format == "MP3":
|
||||
return release.FormatMP3
|
||||
case format == "AAC":
|
||||
return release.FormatAAC
|
||||
case format == "APE":
|
||||
return release.FormatAPE
|
||||
case format == "WV" || format == "WAVPACK":
|
||||
return release.FormatWavPack
|
||||
case format == "ALAC":
|
||||
return release.FormatALAC
|
||||
case format == "OGG":
|
||||
return release.FormatOGG
|
||||
case format == "WAV":
|
||||
return release.FormatWAV
|
||||
default:
|
||||
return release.FormatUnknown
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BaseParser) ExtractBitrate(title string) string {
|
||||
if strings.Contains(strings.ToLower(title), "lossless") {
|
||||
return "lossless"
|
||||
}
|
||||
match := bitratePattern.FindStringSubmatch(title)
|
||||
if len(match) < 2 {
|
||||
return ""
|
||||
}
|
||||
if match[1] != "" {
|
||||
return match[1] + " kbps"
|
||||
}
|
||||
if match[2] != "" {
|
||||
return "V" + match[2]
|
||||
}
|
||||
if match[3] != "" {
|
||||
return "VBR ~" + match[3] + " kbps"
|
||||
}
|
||||
if match[4] != "" && match[5] != "" {
|
||||
return "VBR " + match[4] + "-" + match[5] + " kbps"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *BaseParser) ExtractRipType(title string) string {
|
||||
match := ripTypePattern.FindStringSubmatch(title)
|
||||
if len(match) < 2 {
|
||||
return ""
|
||||
}
|
||||
return strings.ToLower(match[1])
|
||||
}
|
||||
|
||||
func (p *BaseParser) ExtractSource(title string) release.Source {
|
||||
match := sourceTagPattern.FindStringSubmatch(title)
|
||||
if len(match) < 2 {
|
||||
if strings.Contains(strings.ToLower(title), "web") {
|
||||
return release.SourceWEB
|
||||
}
|
||||
return release.SourceUnknown
|
||||
}
|
||||
tag := strings.ToUpper(match[1])
|
||||
switch tag {
|
||||
case "CD":
|
||||
return release.SourceCD
|
||||
case "WEB":
|
||||
return release.SourceWEB
|
||||
case "LP", "VINYL", "MINI-LP", "EP", "12\"", "10\"", "7\"":
|
||||
return release.SourceVinyl
|
||||
case "SACD", "DVDA", "HDAD":
|
||||
return release.SourceDVD
|
||||
default:
|
||||
return release.SourceUnknown
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BaseParser) ExtractHiRes(title string) (bitDepth int, sampleRate int) {
|
||||
match := hiResPattern.FindStringSubmatch(title)
|
||||
if len(match) < 3 {
|
||||
return 0, 0
|
||||
}
|
||||
bitDepth, _ = strconv.Atoi(match[1])
|
||||
sr := match[2]
|
||||
if strings.Contains(sr, ".") {
|
||||
f, _ := strconv.ParseFloat(sr, 64)
|
||||
sampleRate = int(f * 1000)
|
||||
} else {
|
||||
sampleRate, _ = strconv.Atoi(sr)
|
||||
sampleRate *= 1000
|
||||
}
|
||||
return bitDepth, sampleRate
|
||||
}
|
||||
|
||||
func (p *BaseParser) ExtractSpecialTags(title string) []string {
|
||||
matches := specialTagPattern.FindAllStringSubmatch(title, -1)
|
||||
var tags []string
|
||||
for _, match := range matches {
|
||||
if len(match) >= 2 {
|
||||
tags = append(tags, match[1])
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
func (p *BaseParser) ExtractReleaseCount(title string) int {
|
||||
match := releaseCountPattern.FindStringSubmatch(title)
|
||||
if len(match) < 2 {
|
||||
return 0
|
||||
}
|
||||
count, _ := strconv.Atoi(match[1])
|
||||
return count
|
||||
}
|
||||
|
||||
func (p *BaseParser) ExtractLabel(title string) string {
|
||||
match := labelPattern.FindStringSubmatch(title)
|
||||
if len(match) < 2 {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(match[1])
|
||||
}
|
||||
|
||||
func (p *BaseParser) ExtractCatalogNum(title string) string {
|
||||
match := catalogNumPattern.FindStringSubmatch(title)
|
||||
if len(match) < 2 {
|
||||
return ""
|
||||
}
|
||||
return match[1]
|
||||
}
|
||||
|
||||
func (p *BaseParser) DetectType(title string) release.Type {
|
||||
switch {
|
||||
case discographyPattern.MatchString(title):
|
||||
return release.TypeDiscography
|
||||
case collectionPattern.MatchString(title):
|
||||
return release.TypeCollection
|
||||
case bootlegPattern.MatchString(title):
|
||||
return release.TypeBootleg
|
||||
case anthologyPattern.MatchString(title):
|
||||
return release.TypeCollection
|
||||
case soundtrackPattern.MatchString(title):
|
||||
return release.TypeSoundtrack
|
||||
case livePattern.MatchString(title):
|
||||
return release.TypeLive
|
||||
case epPattern.MatchString(title):
|
||||
return release.TypeEP
|
||||
case singlePattern.MatchString(title):
|
||||
return release.TypeSingle
|
||||
case bestOfPattern.MatchString(title):
|
||||
return release.TypeCompilation
|
||||
case compilationPattern.MatchString(title):
|
||||
return release.TypeCompilation
|
||||
default:
|
||||
return release.TypeAlbum
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BaseParser) ExtractArtistAlbum(title string) (artist string, album string) {
|
||||
cleaned := tagsBeforeGenrePattern.ReplaceAllString(title, "")
|
||||
cleaned = p.StripGenrePrefix(cleaned)
|
||||
cleaned = p.StripLeadingTags(cleaned)
|
||||
cleaned = trailingTechPattern.ReplaceAllString(cleaned, "")
|
||||
|
||||
if match := standardTitlePattern.FindStringSubmatch(cleaned); len(match) >= 3 {
|
||||
return strings.TrimSpace(match[1]), strings.TrimSpace(match[2])
|
||||
}
|
||||
|
||||
if match := altTitlePattern.FindStringSubmatch(cleaned); len(match) >= 3 {
|
||||
return strings.TrimSpace(match[1]), strings.TrimSpace(match[2])
|
||||
}
|
||||
|
||||
parts := strings.SplitN(cleaned, " - ", 3)
|
||||
if len(parts) >= 2 {
|
||||
artist = strings.TrimSpace(parts[0])
|
||||
albumPart := strings.TrimSpace(parts[1])
|
||||
albumPart = yearPattern.ReplaceAllString(albumPart, "")
|
||||
albumPart = strings.Trim(albumPart, " -–,")
|
||||
album = albumPart
|
||||
}
|
||||
|
||||
return artist, album
|
||||
}
|
||||
|
||||
func (p *BaseParser) AddError(r *release.Release, err string) {
|
||||
r.ParseErrors = append(r.ParseErrors, err)
|
||||
r.ParsedSuccessfully = false
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type ClassicalParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewClassicalParser() *ClassicalParser {
|
||||
return &ClassicalParser{}
|
||||
}
|
||||
|
||||
func (p *ClassicalParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
r.Genres = []string{"Classical"}
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestClassicalParser(t *testing.T) {
|
||||
p := NewClassicalParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantGenres []string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Rachmaninoff concerto",
|
||||
title: "(Classical) [CD] Rachmaninoff - Piano Concerto No.3 - Nobuyuki Tsujii, Royal Liverpool Philharmonic Orchestra - 2026, FLAC (image+.cue) lossless",
|
||||
wantArtist: "Rachmaninoff",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeAlbum,
|
||||
wantGenres: []string{"Classical"},
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Shostakovich symphonies collection",
|
||||
title: "(Classical) [CD] Dmitry Shostakovich - Symphonies 1-15 (Boston Symphony Orchestra, Andris Nelsons) [19 CDs] - 2025, FLAC (image+.cue) lossless",
|
||||
wantArtist: "Dmitry Shostakovich",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "TR24 OF Brahms symphonies",
|
||||
title: "[TR24][OF] Brahms - The Complete Symphonies (Royal Concertgebouw Orchestra, John Eliot Gardiner) - 2025 (Classical)",
|
||||
wantArtist: "Brahms",
|
||||
wantYear: 2025,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Haitink complete recordings",
|
||||
title: "(Classical) [CD] Bernard Haitink - Concertgebouworkest Edition Complete Studio Recordings [113 CDs] - 2022, FLAC (image+.cue) lossless",
|
||||
wantArtist: "Bernard Haitink",
|
||||
wantYear: 2022,
|
||||
wantType: release.TypeCollection,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Tchaikovsky symphonies",
|
||||
title: "(Classical) [CD] Чайковский - Complete 8 Symphonies plus Concertos [10 CDs] - 2024, FLAC (image+.cue) lossless",
|
||||
wantArtist: "Чайковский",
|
||||
wantYear: 2024,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Wagner opera TR24",
|
||||
title: "[TR24][OF] Wagner - Siegfried (Symphonieorchester des Bayerischen Rundfunks, Sir Simon Rattle) - 2025 (Opera)",
|
||||
wantArtist: "Wagner",
|
||||
wantYear: 2025,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Strauss Elektra opera",
|
||||
title: "[TR24][OF] Irène Theorin, Bergen Philharmonic Orchestra - R. Strauss Elektra Op. 58 - 2026 (Classical, Opera)",
|
||||
wantYear: 2026,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Bruckner symphonies remaster",
|
||||
title: "[TR24][OF] Bruckner - Symphonies Nos. 5 and 6 (New Philharmonia Orchestra, Otto Klemperer) - 2024 (Classical)",
|
||||
wantArtist: "Bruckner",
|
||||
wantYear: 2024,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "DSD Brahms chamber music",
|
||||
title: "[DSD][OF] The Brahms Project - Brahms The Complete Piano Quartets - 2017 (Classical, Chamber Music)",
|
||||
wantArtist: "The Brahms Project",
|
||||
wantYear: 2017,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "DSD Mozart symphonies",
|
||||
title: "[DSD][OF] Concertgebouw Chamber Orchestra - Mozart Symphonies - 2015 (Classical)",
|
||||
wantArtist: "Concertgebouw Chamber Orchestra",
|
||||
wantYear: 2015,
|
||||
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 len(tt.wantGenres) > 0 && len(r.Genres) == 0 {
|
||||
if r.Genres[0] != "Classical" {
|
||||
t.Errorf("Genres[0] = %q, want Classical", r.Genres[0])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
type DiscographyParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewDiscographyParser() *DiscographyParser {
|
||||
return &DiscographyParser{}
|
||||
}
|
||||
|
||||
func (p *DiscographyParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(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.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
|
||||
if collectionPattern.MatchString(title) {
|
||||
r.Type = release.TypeCollection
|
||||
} else {
|
||||
r.Type = release.TypeDiscography
|
||||
}
|
||||
|
||||
r.Artist = p.extractDiscographyArtist(title)
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (p *DiscographyParser) extractDiscographyArtist(title string) string {
|
||||
if match := discographyTitlePattern.FindStringSubmatch(title); len(match) >= 2 {
|
||||
return strings.TrimSpace(match[1])
|
||||
}
|
||||
if match := collectionTitlePattern.FindStringSubmatch(title); len(match) >= 2 {
|
||||
return strings.TrimSpace(match[1])
|
||||
}
|
||||
artist, _ := p.ExtractArtistAlbum(title)
|
||||
return artist
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestDiscographyParser(t *testing.T) {
|
||||
p := NewDiscographyParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantYearEnd int
|
||||
wantReleaseCount int
|
||||
wantType release.Type
|
||||
wantFormat release.AudioFormat
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Russian discography with ALAC",
|
||||
title: "(Metalcore, progressive metalcore, alternative metal, mathcore) [CD`12] Architects - Дискография / Discography - 2006-2025, ALAC (tracks+.cue), lossless",
|
||||
wantArtist: "Architects",
|
||||
wantYear: 2006,
|
||||
wantYearEnd: 2025,
|
||||
wantType: release.TypeDiscography,
|
||||
wantFormat: release.FormatALAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "discography with CD count",
|
||||
title: "(Rock / Hard Rock / Power-Pop) [CD] Cheap Trick - Дискография - 1977-2021 (78 CD), FLAC (image+.cue), lossless",
|
||||
wantArtist: "Cheap Trick",
|
||||
wantYear: 1977,
|
||||
wantYearEnd: 2021,
|
||||
wantReleaseCount: 78,
|
||||
wantType: release.TypeDiscography,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "mixed CD and WEB",
|
||||
title: "Pompeya - Дискография | Discography (3 CD, 6 WEB) - 2011-2015, FLAC (tracks+.cue, tracks/web), lossless",
|
||||
wantArtist: "Pompeya",
|
||||
wantYear: 2011,
|
||||
wantYearEnd: 2015,
|
||||
wantType: release.TypeDiscography,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "large discography with releases count",
|
||||
title: "(Rock) Александр Башлачёв - Дискография (1994-2025) (35 выпусков, 47 CD / 2 Digital Release), FLAC (image+.cue), lossless",
|
||||
wantArtist: "Александр Башлачёв",
|
||||
wantYear: 1994,
|
||||
wantYearEnd: 2025,
|
||||
wantType: release.TypeDiscography,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "very large discography",
|
||||
title: "(Rock) Аквариум и Борис Гребенщиков (БГ) - Дискография - 1973–2023 (222 издания, 245 CD), FLAC (image+.cue), lossless",
|
||||
wantArtist: "Аквариум и Борис Гребенщиков (БГ)",
|
||||
wantYear: 1973,
|
||||
wantYearEnd: 2023,
|
||||
wantType: release.TypeDiscography,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "metal discography",
|
||||
title: "(Heavy Metal) [CD] Saxon - Дискография (58 CD) - 1979-2024, FLAC (image+.cue), lossless",
|
||||
wantArtist: "Saxon",
|
||||
wantYear: 1979,
|
||||
wantYearEnd: 2024,
|
||||
wantReleaseCount: 58,
|
||||
wantType: release.TypeDiscography,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "detailed Queen discography",
|
||||
title: "(Progressive Hard Rock Fusion) [CD] Queen – The Discography / Дискография (15 Studio, 11 Live, 13 Compilation, 63 Singles, 2 Collaboration, 7 Box Set, 243 issues, 336 CD) - 1973-2015, FLAC (image+.cue), lossless",
|
||||
wantArtist: "Queen",
|
||||
wantYear: 1973,
|
||||
wantYearEnd: 2015,
|
||||
wantType: release.TypeDiscography,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "English discography",
|
||||
title: "(Rock, Pop) [CD] U2 - Discography (1980-2017), FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "U2",
|
||||
wantYear: 1980,
|
||||
wantYearEnd: 2017,
|
||||
wantType: release.TypeDiscography,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "death metal discography",
|
||||
title: "(Technical Brutal Death Metal) [CD] Nile - Discography (1994 - 2024) 13 CD, FLAC (image+.cue), lossless",
|
||||
wantArtist: "Nile",
|
||||
wantYear: 1994,
|
||||
wantYearEnd: 2024,
|
||||
wantReleaseCount: 13,
|
||||
wantType: release.TypeDiscography,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "collection keyword",
|
||||
title: "(Pop) Madonna - Коллекция / Collection - 65 релизов (2 Albums, 22 Singles, 13 Megamixes, 8 Live, 17 Collections, 3 Bonus) (1982-2012), MP3, 128-320, VBR kbps",
|
||||
wantArtist: "Madonna",
|
||||
wantYear: 1982,
|
||||
wantYearEnd: 2012,
|
||||
wantType: release.TypeCollection,
|
||||
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.wantYearEnd != 0 && r.YearEnd != tt.wantYearEnd {
|
||||
t.Errorf("YearEnd = %d, want %d", r.YearEnd, tt.wantYearEnd)
|
||||
}
|
||||
|
||||
if tt.wantReleaseCount != 0 && r.ReleaseCount != tt.wantReleaseCount {
|
||||
t.Errorf("ReleaseCount = %d, want %d", r.ReleaseCount, tt.wantReleaseCount)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type GeneralParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewGeneralParser() *GeneralParser {
|
||||
return &GeneralParser{}
|
||||
}
|
||||
|
||||
func (p *GeneralParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
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
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestGeneralParser(t *testing.T) {
|
||||
p := NewGeneralParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantAlbum string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantGenres []string
|
||||
wantSource release.Source
|
||||
wantRipType string
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "standard CD rip with genre",
|
||||
title: "(Rock) [CD] Thin Lizzy - Acoustic Sessions - 2024 (Decca Records EU 2025), FLAC (image+.cue), lossless",
|
||||
wantArtist: "Thin Lizzy",
|
||||
wantAlbum: "Acoustic Sessions",
|
||||
wantYear: 2024,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeAlbum,
|
||||
wantGenres: []string{"Rock"},
|
||||
wantSource: release.SourceCD,
|
||||
wantRipType: "image+.cue",
|
||||
wantBitrate: "lossless",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "multi-genre CD rip",
|
||||
title: "(Hard Rock, Glam Rock, Progressive Rock, Art Rock, Heavy Metal) [CD] Queen – Queen I (2 CD) – 2024 , FLAC (image+.cue), lossless",
|
||||
wantArtist: "Queen",
|
||||
wantYear: 2024,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeAlbum,
|
||||
wantGenres: []string{"Hard Rock", "Glam Rock", "Progressive Rock", "Art Rock", "Heavy Metal"},
|
||||
wantSource: release.SourceCD,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "WEB release with tracks",
|
||||
title: "(Progressive Rock) [WEB] Opeth - In Cauda Venenum (Extended Edition) - 2019/2022, FLAC (tracks), lossless",
|
||||
wantArtist: "Opeth",
|
||||
wantYear: 2019,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantSource: release.SourceWEB,
|
||||
wantRipType: "tracks",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Japan release",
|
||||
title: "(Pop-Rock Soft-Rock) [CD] Sting - The Soul Cages (Expanded Edition) - 2025 [Japan], FLAC (image+.cue), lossless",
|
||||
wantArtist: "Sting",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantSource: release.SourceCD,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "live album",
|
||||
title: "(Rock) [CD] Bryan Adams - Live at the Royal Albert Hall - 2024, FLAC (image+.cue), lossless",
|
||||
wantArtist: "Bryan Adams",
|
||||
wantType: release.TypeLive,
|
||||
wantYear: 2024,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "soundtrack",
|
||||
title: "(Pop) [CD] Celine Dion - I AM - Celine Dion (Original Motion Picture Soundtrack) - 2024 [Japan], FLAC (image+.cue), lossless",
|
||||
wantArtist: "Celine Dion",
|
||||
wantType: release.TypeSoundtrack,
|
||||
wantYear: 2024,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "deluxe box set",
|
||||
title: "(Rock) [CD] Bryan Adams - Roll With The Punches (Deluxe Box Set) - 2025, FLAC (image+.cue), lossless",
|
||||
wantArtist: "Bryan Adams",
|
||||
wantYear: 2025,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "CDS single",
|
||||
title: "(Heavy Metal) [CDS] Bruce Dickinson - Resurrection Men - 2024, FLAC (image+.cue), lossless",
|
||||
wantArtist: "Bruce Dickinson",
|
||||
wantYear: 2024,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "tracks+cue format",
|
||||
title: "(Classic Rock) [CD] The Who - Who Are You (Super Deluxe Edition) - 2025, FLAC (tracks+cue), lossless",
|
||||
wantArtist: "The Who",
|
||||
wantYear: 2025,
|
||||
wantRipType: "tracks+cue",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "WEB with special artist name",
|
||||
title: "(Chamber Pop) [WEB] Florence + the Machine - Ceremonials (Digital Deluxe Edition) - 2011, FLAC (tracks), lossless",
|
||||
wantArtist: "Florence + the Machine",
|
||||
wantYear: 2011,
|
||||
wantSource: release.SourceWEB,
|
||||
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.wantAlbum != "" && r.Album != tt.wantAlbum {
|
||||
t.Errorf("Album = %q, want %q", r.Album, tt.wantAlbum)
|
||||
}
|
||||
|
||||
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.wantSource != release.SourceUnknown && r.Source != tt.wantSource {
|
||||
t.Errorf("Source = %v, want %v", r.Source, tt.wantSource)
|
||||
}
|
||||
|
||||
if tt.wantRipType != "" && r.RipType != tt.wantRipType {
|
||||
t.Errorf("RipType = %q, want %q", r.RipType, tt.wantRipType)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
|
||||
if len(tt.wantGenres) > 0 {
|
||||
if len(r.Genres) != len(tt.wantGenres) {
|
||||
t.Errorf("Genres count = %d, want %d", len(r.Genres), len(tt.wantGenres))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type HiResParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewHiResParser() *HiResParser {
|
||||
return &HiResParser{}
|
||||
}
|
||||
|
||||
func (p *HiResParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
|
||||
if r.Format == release.FormatUnknown {
|
||||
r.Format = release.FormatFLAC
|
||||
}
|
||||
r.Bitrate = "lossless"
|
||||
|
||||
if r.BitDepth == 0 {
|
||||
if dsdMatch := dsdPattern.FindStringSubmatch(title); len(dsdMatch) >= 3 {
|
||||
r.BitDepth = 1
|
||||
r.Tags = append(r.Tags, dsdMatch[1]+dsdMatch[2])
|
||||
}
|
||||
}
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestHiResParser(t *testing.T) {
|
||||
p := NewHiResParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantBitDepth int
|
||||
wantSampleRate int
|
||||
wantSource release.Source
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "TR24 OF official release",
|
||||
title: "[TR24][OF] Matteo Mancuso - Route 96 - 2026 (Progressive Rock, Jazz Fusion, Instrumental)",
|
||||
wantArtist: "Matteo Mancuso",
|
||||
wantYear: 2026,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "TR24 OF LDR tag",
|
||||
title: "[TR24][OF][LDR] Sepultura - The Cloud Of Unknowing - 2026 (Groove Thrash Metal)",
|
||||
wantArtist: "Sepultura",
|
||||
wantYear: 2026,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "24bit 48kHz in title",
|
||||
title: "[TR24][OF] U2 - Days Of Ash [EP] [24bit-48kHz] - 2026 (Pop Rock, Soft Rock)",
|
||||
wantArtist: "U2",
|
||||
wantYear: 2026,
|
||||
wantBitDepth: 24,
|
||||
wantSampleRate: 48000,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "LP 24/192",
|
||||
title: "(Blues, R&B) [LP] [24/192] Etta James - At Last! - 1960/2026, FLAC (tracks)",
|
||||
wantArtist: "Etta James",
|
||||
wantYear: 1960,
|
||||
wantBitDepth: 24,
|
||||
wantSampleRate: 192000,
|
||||
wantSource: release.SourceVinyl,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "DSD128",
|
||||
title: "(Progressive rock) [LP] [1/5,64 MHz] The Neal Morse Band – L. I. F. T. - 2026, DSD 128 (tracks)",
|
||||
wantArtist: "The Neal Morse Band",
|
||||
wantYear: 2026,
|
||||
wantSource: release.SourceVinyl,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "DSD256 with label",
|
||||
title: "(Jazz, Bop) [LP] [DSD256] Oscar Peterson Trio & Clark Terry - Oscar Peterson Trio + One [Acoustic Sounds Series] - 1964, dsf (tracks)",
|
||||
wantArtist: "Oscar Peterson Trio & Clark Terry",
|
||||
wantYear: 1964,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "24/96 modal jazz",
|
||||
title: "(Modal, Jazz) [LP] [24/96] John Coltrane - The Tiberi Tapes: A Preview Of The Mythic Recordings (2026 Record Store Day) - 2026, FLAC (tracks)",
|
||||
wantArtist: "John Coltrane",
|
||||
wantYear: 2026,
|
||||
wantBitDepth: 24,
|
||||
wantSampleRate: 96000,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "2xLP compilation",
|
||||
title: "(Electronic, Funk / Soul, Disco, House) [2xLP] [24/192] Various - The Many Faces Of Daft Punk - 2020( Compilation), FLAC (tracks)",
|
||||
wantArtist: "Various",
|
||||
wantYear: 2020,
|
||||
wantBitDepth: 24,
|
||||
wantSampleRate: 192000,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "SACD-R",
|
||||
title: "[SACD-R][OF] Wynton Marsalis - The London Concert - 2000 (Classical)",
|
||||
wantArtist: "Wynton Marsalis",
|
||||
wantYear: 2000,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "SACD-R DSD",
|
||||
title: "[SACD-R][DSD][OF]Scott Hamilton, Paolo Birro - Pure Imagination - 2019 (Jazz)",
|
||||
wantArtist: "Scott Hamilton, Paolo Birro",
|
||||
wantYear: 2019,
|
||||
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.wantBitDepth != 0 && r.BitDepth != tt.wantBitDepth {
|
||||
t.Errorf("BitDepth = %d, want %d", r.BitDepth, tt.wantBitDepth)
|
||||
}
|
||||
|
||||
if tt.wantSampleRate != 0 && r.SampleRate != tt.wantSampleRate {
|
||||
t.Errorf("SampleRate = %d, want %d", r.SampleRate, tt.wantSampleRate)
|
||||
}
|
||||
|
||||
if tt.wantSource != release.SourceUnknown && r.Source != tt.wantSource {
|
||||
t.Errorf("Source = %v, want %v", r.Source, tt.wantSource)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type JazzParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewJazzParser() *JazzParser {
|
||||
return &JazzParser{}
|
||||
}
|
||||
|
||||
func (p *JazzParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
r.Genres = []string{"Jazz"}
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestJazzParser(t *testing.T) {
|
||||
p := NewJazzParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantSource release.Source
|
||||
wantType release.Type
|
||||
wantBitDepth int
|
||||
wantSampleRate int
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Coltrane DSD256 vinyl",
|
||||
title: "(Jazz, Post Bop, Modal) [LP] [DSD256] The John Coltrane Quartet - The John Coltrane Quartet Plays - 1965, dsf (tracks)",
|
||||
wantArtist: "The John Coltrane Quartet",
|
||||
wantYear: 1965,
|
||||
wantSource: release.SourceVinyl,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Coltrane modal jazz CD",
|
||||
title: "(Modal Jazz, Hard Bop, Saxophone Jazz) [CD] John Coltrane - Coltrane Jazz - 1961, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "John Coltrane",
|
||||
wantYear: 1961,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantSource: release.SourceCD,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "TR24 bebop",
|
||||
title: "[TR24][OF] Alan Broadbent - Threads of Time - 2025 (Bebop)",
|
||||
wantArtist: "Alan Broadbent",
|
||||
wantYear: 2025,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Japanese jazz compilation",
|
||||
title: "(Fusion, Post-Bop, Modal) [CD] VA - J Jazz Deep Modern Jazz from Japan 1969-1984 - 2018, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2018,
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Fusion WEB release",
|
||||
title: "(Fusion, Post-Fusion) [WEB] Tucson Modern Jazz Quartet - Eight Myths - 2025, FLAC (tracks), lossless",
|
||||
wantArtist: "Tucson Modern Jazz Quartet",
|
||||
wantYear: 2025,
|
||||
wantSource: release.SourceWEB,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Miles Davis live vinyl 24/96",
|
||||
title: "(Jazz Rock, Fusion, Psychedelic) [2xLP] [24/96] Miles Davis - Live in Tokyo 1975 - 2015, FLAC (image+.cue)",
|
||||
wantArtist: "Miles Davis",
|
||||
wantYear: 2015,
|
||||
wantType: release.TypeLive,
|
||||
wantBitDepth: 24,
|
||||
wantSampleRate: 96000,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Miles Davis Plugged Nickel 24/192",
|
||||
title: "(Jazz) [LP] [24/192] Miles Davis - Live At The Plugged Nickel December 22 1965 - 2013, FLAC (image+.cue)",
|
||||
wantArtist: "Miles Davis",
|
||||
wantYear: 2013,
|
||||
wantType: release.TypeLive,
|
||||
wantBitDepth: 24,
|
||||
wantSampleRate: 192000,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Contemporary jazz CD",
|
||||
title: "(Post-Bop, Contemporary Jazz) [CD] Billy Hart - Multidirectional - 2025, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Billy Hart",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Herbie Mann live mono vinyl",
|
||||
title: "(Jazz, Hard Bop) [LP] [24/192] Herbie Mann - Herbie Mann At The Village Gate - 1962, FLAC (image+.cue)",
|
||||
wantArtist: "Herbie Mann",
|
||||
wantYear: 1962,
|
||||
wantType: release.TypeLive,
|
||||
wantBitDepth: 24,
|
||||
wantSampleRate: 192000,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Smooth jazz WEB",
|
||||
title: "(Smooth Jazz) [WEB] VA - Smooth Jazz Plays Your Favorite Hits - 2006, FLAC (tracks), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2006,
|
||||
wantSource: release.SourceWEB,
|
||||
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.wantSource != release.SourceUnknown && r.Source != tt.wantSource {
|
||||
t.Errorf("Source = %v, want %v", r.Source, tt.wantSource)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
|
||||
if tt.wantBitDepth != 0 && r.BitDepth != tt.wantBitDepth {
|
||||
t.Errorf("BitDepth = %d, want %d", r.BitDepth, tt.wantBitDepth)
|
||||
}
|
||||
|
||||
if tt.wantSampleRate != 0 && r.SampleRate != tt.wantSampleRate {
|
||||
t.Errorf("SampleRate = %d, want %d", r.SampleRate, tt.wantSampleRate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type LabelPacksParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewLabelPacksParser() *LabelPacksParser {
|
||||
return &LabelPacksParser{}
|
||||
}
|
||||
|
||||
func (p *LabelPacksParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
r.Type = release.TypeCollection
|
||||
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.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
|
||||
if r.Label == "" {
|
||||
p.AddError(r, "failed to extract label name")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestLabelPacksParser(t *testing.T) {
|
||||
p := NewLabelPacksParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantLabel string
|
||||
wantYear int
|
||||
wantYearEnd int
|
||||
wantReleaseCount int
|
||||
wantFormat release.AudioFormat
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "standard label pack",
|
||||
title: "(Drum & Bass) [WEB] Label: Metalheadz (370 релизов), 1994-2025, FLAC (tracks), lossless",
|
||||
wantLabel: "Metalheadz",
|
||||
wantYear: 1994,
|
||||
wantYearEnd: 2025,
|
||||
wantReleaseCount: 370,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "label with part number",
|
||||
title: "(Trance, House) [WEB] Label: Black Hole Recordings Part 3 (401 Releases) - 2009-2023, FLAC (tracks / images), lossless",
|
||||
wantLabel: "Black Hole Recordings Part 3",
|
||||
wantYear: 2009,
|
||||
wantYearEnd: 2023,
|
||||
wantReleaseCount: 401,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "small label",
|
||||
title: "(Trance) [WEB, CD] Label: Solaris Recordings (7 Releases) - 2005-2014, FLAC (tracks, tracks+.cue), lossless",
|
||||
wantLabel: "Solaris Recordings",
|
||||
wantYear: 2005,
|
||||
wantYearEnd: 2014,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "techno label with brackets",
|
||||
title: "(Techno, IDM, Experimental) [WEB,CD] Label: Stroboscopic Artefacts (96 Releases) - 2009-2022, FLAC (tracks) (tracks+.cue), lossless",
|
||||
wantLabel: "Stroboscopic Artefacts",
|
||||
wantYear: 2009,
|
||||
wantYearEnd: 2022,
|
||||
wantReleaseCount: 96,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "multi-genre label",
|
||||
title: "(Techno, Ambient, IDM, Experimental, Drum n Bass) [WEB,CD] Label: Auxiliary (65 Releases) - 2010-2021, FLAC (tracks) (tracks+.cue), lossless",
|
||||
wantLabel: "Auxiliary",
|
||||
wantYear: 2010,
|
||||
wantYearEnd: 2021,
|
||||
wantReleaseCount: 65,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Russian release count",
|
||||
title: "(Techno, Minimal, Deep Tech, Melodic House & Techno) [WEB] Label: FCKNG SERIOUS (121 релиз), 2015-2025, FLAC (tracks, image), lossless",
|
||||
wantLabel: "FCKNG SERIOUS",
|
||||
wantYear: 2015,
|
||||
wantYearEnd: 2025,
|
||||
wantReleaseCount: 121,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "progressive house label",
|
||||
title: "(Progressive House, Trance, Techno) [WEB] Label: Bedrock Records (519 релизов), 1999-2025, (FLAC) lossless (tracks, image)",
|
||||
wantLabel: "Bedrock Records",
|
||||
wantYear: 1999,
|
||||
wantYearEnd: 2025,
|
||||
wantReleaseCount: 519,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "large techno label",
|
||||
title: "(Techno) [WEB,CD] Label: Planet Rhythm Records (443 Releases) - 1994-2021, FLAC (tracks) (tracks+.cue, image+.cue), lossless",
|
||||
wantLabel: "Planet Rhythm Records",
|
||||
wantYear: 1994,
|
||||
wantYearEnd: 2021,
|
||||
wantReleaseCount: 443,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "label with featured artists",
|
||||
title: "(Trance, Breaks, House) [WEB] Label: Digital Emotions (47 Releases) (Incl. Fonarev pres. F13, Poshout, Second Sine & etc.) - 2010-2025, FLAC (tracks), lossless",
|
||||
wantLabel: "Digital Emotions",
|
||||
wantYear: 2010,
|
||||
wantYearEnd: 2025,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "bondage music",
|
||||
title: "(Deep House, Minimal) [WEB] Label: Bondage Music (173 релиза), 2006-2025, (FLAC) lossless (tracks, image)",
|
||||
wantLabel: "Bondage Music",
|
||||
wantYear: 2006,
|
||||
wantYearEnd: 2025,
|
||||
wantReleaseCount: 173,
|
||||
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.wantLabel != "" && r.Label != tt.wantLabel {
|
||||
t.Errorf("Label = %q, want %q", r.Label, tt.wantLabel)
|
||||
}
|
||||
|
||||
if tt.wantYear != 0 && r.Year != tt.wantYear {
|
||||
t.Errorf("Year = %d, want %d", r.Year, tt.wantYear)
|
||||
}
|
||||
|
||||
if tt.wantYearEnd != 0 && r.YearEnd != tt.wantYearEnd {
|
||||
t.Errorf("YearEnd = %d, want %d", r.YearEnd, tt.wantYearEnd)
|
||||
}
|
||||
|
||||
if tt.wantReleaseCount != 0 && r.ReleaseCount != tt.wantReleaseCount {
|
||||
t.Errorf("ReleaseCount = %d, want %d", r.ReleaseCount, tt.wantReleaseCount)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if r.Type != release.TypeCollection {
|
||||
t.Errorf("Type = %v, want Collection", r.Type)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type LosslessParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewLosslessParser() *LosslessParser {
|
||||
return &LosslessParser{}
|
||||
}
|
||||
|
||||
func (p *LosslessParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Source = p.ExtractSource(title)
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Format == release.FormatUnknown {
|
||||
r.Format = release.FormatFLAC
|
||||
}
|
||||
r.Bitrate = "lossless"
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestLosslessParser(t *testing.T) {
|
||||
p := NewLosslessParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantSource release.Source
|
||||
wantRipType string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "standard CD FLAC image",
|
||||
title: "(Rock) [CD] Thin Lizzy - Acoustic Sessions - 2024 (Decca Records EU 2025), FLAC (image+.cue), lossless",
|
||||
wantArtist: "Thin Lizzy",
|
||||
wantYear: 2024,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantSource: release.SourceCD,
|
||||
wantRipType: "image+.cue",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "WEB release tracks",
|
||||
title: "(Progressive Rock) [WEB] Opeth - In Cauda Venenum (Extended Edition) - 2019/2022, FLAC (tracks), lossless",
|
||||
wantArtist: "Opeth",
|
||||
wantYear: 2019,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantSource: release.SourceWEB,
|
||||
wantRipType: "tracks",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "APE format",
|
||||
title: "(Jazz) [CD] Miles Davis - Kind of Blue - 1959, APE (image+.cue), lossless",
|
||||
wantArtist: "Miles Davis",
|
||||
wantYear: 1959,
|
||||
wantFormat: release.FormatAPE,
|
||||
wantSource: release.SourceCD,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "tracks+cue format",
|
||||
title: "(Classic Rock) [CD] The Who - Who Are You (Super Deluxe Edition) - 2025, FLAC (tracks+cue), lossless",
|
||||
wantArtist: "The Who",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantRipType: "tracks+cue",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "multi-disc set",
|
||||
title: "(Hard Rock, Glam Rock, Progressive Rock, Art Rock, Heavy Metal) [CD] Queen – Queen I (2 CD) – 2024 , FLAC (image+.cue), lossless",
|
||||
wantArtist: "Queen",
|
||||
wantYear: 2024,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Japan release",
|
||||
title: "(Pop-Rock Soft-Rock) [CD] Sting - The Soul Cages (Expanded Edition) - 2025 [Japan], FLAC (image+.cue), lossless",
|
||||
wantArtist: "Sting",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "WavPack format",
|
||||
title: "(Progressive Rock) [CD] Yes - Close to the Edge - 1972, WV (image+.cue), lossless",
|
||||
wantArtist: "Yes",
|
||||
wantYear: 1972,
|
||||
wantFormat: release.FormatWavPack,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "default to FLAC when format not specified",
|
||||
title: "(Rock) [CD] Pink Floyd - The Wall - 1979 (image+.cue), lossless",
|
||||
wantArtist: "Pink Floyd",
|
||||
wantYear: 1979,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "heavy metal WEB",
|
||||
title: "(Heavy Metal) [WEB] Heaven & Hell - Breaking Out Of Heaven - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Heaven & Hell",
|
||||
wantYear: 2026,
|
||||
wantSource: release.SourceWEB,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "melodic rock WEB",
|
||||
title: "(Melodic Rock, Progressive Rock) [WEB] James LaBrie - Beautiful Shade Of Grey - 2022, FLAC (tracks), lossless",
|
||||
wantArtist: "James LaBrie",
|
||||
wantYear: 2022,
|
||||
wantSource: release.SourceWEB,
|
||||
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.wantSource != release.SourceUnknown && r.Source != tt.wantSource {
|
||||
t.Errorf("Source = %v, want %v", r.Source, tt.wantSource)
|
||||
}
|
||||
|
||||
if tt.wantRipType != "" && r.RipType != tt.wantRipType {
|
||||
t.Errorf("RipType = %q, want %q", r.RipType, tt.wantRipType)
|
||||
}
|
||||
|
||||
if r.Bitrate != "lossless" {
|
||||
t.Errorf("Bitrate = %q, want lossless", r.Bitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type LossyParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewLossyParser() *LossyParser {
|
||||
return &LossyParser{}
|
||||
}
|
||||
|
||||
func (p *LossyParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
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.Tags = p.ExtractSpecialTags(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
|
||||
if r.Format == release.FormatUnknown {
|
||||
r.Format = release.FormatMP3
|
||||
}
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestLossyParser(t *testing.T) {
|
||||
p := NewLossyParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantBitrate string
|
||||
wantType release.Type
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "VBR V0",
|
||||
title: "(Pop) VA - Pop Classics Top 100 - 2012, MP3, VBR V0",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2012,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "V0",
|
||||
wantType: release.TypeCompilation,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "VBR V0 kbps suffix",
|
||||
title: "(Pop/Rock) VA - 101 Ultimate 80's (5 CD) - 2011, MP3 (tracks), VBR V0 kbps",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2011,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "V0",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "VBR V1",
|
||||
title: "(Rock) VA - Greatest Ever! Rock The Definitive Collection (3 CD) - 2006, MP3 (tracks), VBR V1 kbps",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2006,
|
||||
wantBitrate: "V1",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "VBR V2",
|
||||
title: "(Classic Rock) VA - Twist & Shout - 2005, MP3, VBR V2",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2005,
|
||||
wantBitrate: "V2",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "VBR range",
|
||||
title: "(Pop, Rock) VA - The Essential 1980s - 2010, MP3 (tracks), VBR 192-320 kbps",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2010,
|
||||
wantBitrate: "VBR 192-320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "CBR 320",
|
||||
title: "(Pop) VA - Bravo Hits, Vol. 128 [2 CD] - 2025, MP3, 320 kbps",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2025,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "CBR 256",
|
||||
title: "(Rock'n'Roll) VA - Rock-n-roll The Best Hits - 2005, MP3 (tracks), 256 kbps",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2005,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantBitrate: "256 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "year range in title",
|
||||
title: "(Pop) VA - Bravo Hits vol. 31-59 - 2000-2007, MP3, VBR 192-320 kbps",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2000,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "discography in lossy",
|
||||
title: "(Alternative Metal / Post-Grunge) Breaking Benjamin - Discography: 23 Releases, 2001-2024, MP3, VBR V0/320 kbps",
|
||||
wantArtist: "Breaking Benjamin",
|
||||
wantYear: 2001,
|
||||
wantType: release.TypeDiscography,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "bootleg release",
|
||||
title: "(Eurodance) VA - Beat Mix Eurodance Vol 1-3 (Bootlegs) - 2009-2011, MP3 (image), VBR V2 / V0",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2009,
|
||||
wantType: release.TypeBootleg,
|
||||
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.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
|
||||
if tt.wantType != release.TypeUnknown && r.Type != tt.wantType {
|
||||
t.Errorf("Type = %v, want %v", r.Type, tt.wantType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type MetalParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewMetalParser() *MetalParser {
|
||||
return &MetalParser{}
|
||||
}
|
||||
|
||||
func (p *MetalParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
if len(r.Genres) == 0 {
|
||||
r.Genres = []string{"Metal"}
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestMetalParser(t *testing.T) {
|
||||
p := NewMetalParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Death metal EP",
|
||||
title: "(Death Metal) Monolithic Terror - A Time To Kill (EP) - 2026, MP3, 320 kbps",
|
||||
wantArtist: "Monolithic Terror",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantType: release.TypeEP,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Heavy metal album",
|
||||
title: "(Heavy Metal) More - Destructor - 2026, MP3, 320 kbps",
|
||||
wantArtist: "More",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Melodic death metal EP",
|
||||
title: "(Melodic Death Metal) Death Brigade - Rites Of War (EP) - 2026, MP3, 320 kbps",
|
||||
wantArtist: "Death Brigade",
|
||||
wantYear: 2026,
|
||||
wantType: release.TypeEP,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Power metal WEB FLAC",
|
||||
title: "(Heavy Metal, Power Metal) [WEB] Death Dealer - Reign of Steel - 2026, FLAC (tracks), lossless",
|
||||
wantArtist: "Death Dealer",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Thrash metal deluxe box",
|
||||
title: "(Heavy/Power/Thrash Metal) Metal Church - Dead to Rights (Deluxe Box Set Edition) - 2026, MP3, 320 kbps",
|
||||
wantArtist: "Metal Church",
|
||||
wantYear: 2026,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Iron Maiden discography",
|
||||
title: "(Heavy Metal, Hard Rock) Iron Maiden - Discography (146 CD + 4 WEB) - 1979-2021, AAC (tracks), VBR 320 kbps",
|
||||
wantArtist: "Iron Maiden",
|
||||
wantYear: 1979,
|
||||
wantType: release.TypeDiscography,
|
||||
wantFormat: release.FormatAAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Black metal restored",
|
||||
title: "[RM] [restored] [declipped] [16/44] (Black Metal) Mayhem - 15 releases - 1987-2026, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Mayhem",
|
||||
wantYear: 1987,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Black metal vinyl 24/96",
|
||||
title: "(Black Metal) [LP] [24/96] Hellhammer - Apocalyptic Raids - 1984, FLAC (tracks)",
|
||||
wantArtist: "Hellhammer",
|
||||
wantYear: 1984,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Russian thrash vinyl rip",
|
||||
title: "(Thrash Metal) КОРРОЗИЯ МЕТАЛЛА - Каннибал (VINYL RIP) - 1990, FLAC (image+.cue), lossless",
|
||||
wantArtist: "КОРРОЗИЯ МЕТАЛЛА",
|
||||
wantYear: 1990,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Progressive metal live",
|
||||
title: "(Progressive Metal) Leprous - An Evening of Atonement (Live in Tilburg 2025) [2 CD] - 2025, MP3, 320 kbps",
|
||||
wantArtist: "Leprous",
|
||||
wantYear: 2025,
|
||||
wantType: release.TypeLive,
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package parser
|
||||
|
||||
import "regexp"
|
||||
|
||||
var (
|
||||
// Genre at start: (Rock), (Electronic, Ambient), (Jazz / Blues)
|
||||
genrePattern = regexp.MustCompile(`^\s*\(([^)]+)\)\s*`)
|
||||
|
||||
// Label pack: Label: Name or Label - Name
|
||||
labelPattern = regexp.MustCompile(`(?i)Label\s*[:\-]\s*([^\[(]+?)(?:\s*[\[(]|\s*$)`)
|
||||
|
||||
// Year: single or range
|
||||
yearPattern = regexp.MustCompile(`\b((?:19|20)\d{2})\b`)
|
||||
yearRangePattern = regexp.MustCompile(`\b((?:19|20)\d{2})\s*[-–]\s*((?:19|20)\d{2})\b`)
|
||||
|
||||
// Reissue year format: 1960/2026 (original/reissue) → capture first
|
||||
reissueYearPattern = regexp.MustCompile(`\b((?:19|20)\d{2})/((?:19|20)\d{2})\b`)
|
||||
|
||||
// Release year after dash: " - YEAR" or " - YEAR," or " - YEAR ("
|
||||
releaseYearPattern = regexp.MustCompile(`\s[-–]\s*((?:19|20)\d{2})(?:[,\s(]|$)`)
|
||||
|
||||
// Release count: (15 CD), (30 albums), 10 releases, (50 релизов), 13 CD
|
||||
releaseCountPattern = regexp.MustCompile(`(?i)(?:\()?(\d+)\s*(?:CD|albums?|releases?|релиз(?:а|ов)?|альбом(?:а|ов)?)(?:\))?`)
|
||||
|
||||
// Audio formats
|
||||
formatPattern = regexp.MustCompile(`(?i)\b(FLAC|APE|MP3|AAC|OGG|WV|WavPack|ALAC|WAV|DSD\d*|DST\d*)\b`)
|
||||
|
||||
// Bitrate: 320 kbps, V0, VBR 192-320 kbps, lossless
|
||||
bitratePattern = regexp.MustCompile(`(?i)(?:(\d{2,3})\s*kbps|V([012])|VBR\s*(?:~?(\d+)|(\d+)-(\d+))\s*kbps|lossless)`)
|
||||
|
||||
// Rip type: image+.cue, tracks+.cue, tracks
|
||||
ripTypePattern = regexp.MustCompile(`(?i)(image\+\.?cue|tracks?\+\.?cue|tracks?)`)
|
||||
|
||||
// Hi-Res bit depth / sample rate: [24/96], [24/192], [24bit-48kHz]
|
||||
hiResPattern = regexp.MustCompile(`\[(\d+)(?:/|bit[/-])(\d+(?:\.\d+)?)\s*(?:kHz)?\]`)
|
||||
|
||||
// DSD formats: DSD64, DSD128, DST64
|
||||
dsdPattern = regexp.MustCompile(`(?i)\b(DSD|DST)(64|128|256|512)\b`)
|
||||
|
||||
// Source tags: [CD], [WEB], [LP], [Vinyl], [SACD], [DVDA]
|
||||
sourceTagPattern = regexp.MustCompile(`(?i)\[(CD|WEB|LP|Vinyl|SACD|DVDA|HDAD|MINI-LP|EP|12"|10"|7")\]`)
|
||||
|
||||
// Vinyl condition: [NM], [EX], [VG+], [VG], [G], [Mint], [SS]
|
||||
vinylConditionPattern = regexp.MustCompile(`\[(Mint|SS|NM|EX|VG\+?|G|F/?P)\]`)
|
||||
|
||||
// Special tags: [AI], [WEB], [TR24], [OF], [RM], [restored], [declipped]
|
||||
specialTagPattern = regexp.MustCompile(`\[(AI|WEB|TR24|OF|RM|restored|declipped)\]`)
|
||||
|
||||
// Discography keywords (Russian + English)
|
||||
discographyPattern = regexp.MustCompile(`(?i)\b([Дд]искографи[яи]|[Dd]iscograph(?:y|ies))\b`)
|
||||
|
||||
// Collection keywords
|
||||
collectionPattern = regexp.MustCompile(`(?i)\b([Кк]оллекци[яи]|[Cc]ollection|[Cc]omplete\s+(?:[Ss]tudio\s+)?[Rr]ecordings?)\b`)
|
||||
|
||||
// Compilation keywords
|
||||
compilationPattern = regexp.MustCompile(`(?i)\b([Сс]борник|[Cc]ompilation|[Vv]arious\s*[Aa]rtists?|VA)\b`)
|
||||
|
||||
// Anthology keywords
|
||||
anthologyPattern = regexp.MustCompile(`(?i)\b([Аа]нтологи[яи]|[Aa]nthology)\b`)
|
||||
|
||||
// Best of / Greatest hits keywords
|
||||
bestOfPattern = regexp.MustCompile(`(?i)\b([Ии]збранное|[Лл]учшее|[Bb]est\s*[Oo]f|[Gg]reatest\s*[Hh]its)\b`)
|
||||
|
||||
// Live / Concert keywords including venue patterns
|
||||
livePattern = regexp.MustCompile(`(?i)(\b[Жж]ивой\b|\b[Кк]онцерт\b|\b[Ll]ive\b|\b[Cc]oncert\b|[Ll]ive\s*[Aa]t|[Aa]t\s+[Tt]he\s+\w+)`)
|
||||
|
||||
// Bootleg keywords
|
||||
bootlegPattern = regexp.MustCompile(`(?i)\b([Бб]утлеги?|[Bb]ootlegs?|[Uu]nofficial)\b`)
|
||||
|
||||
// Soundtrack keywords
|
||||
soundtrackPattern = regexp.MustCompile(`(?i)\b(OST|[Ss]oundtrack|[Сс]аундтрек|[Ss]core|[Мм]узыка\s*(?:к|из)\s*фильм[ау])\b`)
|
||||
|
||||
// Remaster keywords
|
||||
remasterPattern = regexp.MustCompile(`(?i)\b([Рр]емастер|[Rr]emaster(?:ed)?|[Пп]ереиздани[ея]|[Rr]e-?issue)\b`)
|
||||
|
||||
// EP keywords
|
||||
epPattern = regexp.MustCompile(`(?i)\b(EP|[Мм]ини[-\s]?[Аа]льбом|[Ee]xtended\s*[Pp]lay)\b`)
|
||||
|
||||
// Single keywords
|
||||
singlePattern = regexp.MustCompile(`(?i)\b([Сс]ингл|[Ss]ingle)\b`)
|
||||
|
||||
// Standard title format: Artist - Album - Year or (Genre) Artist - Album - Year
|
||||
// Captures: artist, album, year
|
||||
standardTitlePattern = regexp.MustCompile(`^(?:\([^)]+\)\s*)?(?:\[[^\]]+\]\s*)*([^-–]+?)\s*[-–]\s*(.+?)\s*[-–]\s*((?:19|20)\d{2})`)
|
||||
|
||||
// Alternative: Artist - Album (Year)
|
||||
altTitlePattern = regexp.MustCompile(`^(?:\([^)]+\)\s*)?(?:\[[^\]]+\]\s*)*([^-–]+?)\s*[-–]\s*(.+?)\s*\(((?:19|20)\d{2})\)`)
|
||||
|
||||
// Discography title: Artist - Дискография (15 CD) [1990-2020, ...]
|
||||
discographyTitlePattern = regexp.MustCompile(`^(?:\([^)]+\)\s*)?(?:\[[^\]]+\]\s*)*([^-–]+?)\s*[-–]\s*(?:[Дд]искографи[яи]|[Dd]iscograph(?:y|ies))`)
|
||||
|
||||
// Collection title: Artist - Коллекция (50 CD) [1980-2019, ...]
|
||||
collectionTitlePattern = regexp.MustCompile(`^(?:\([^)]+\)\s*)?(?:\[[^\]]+\]\s*)*([^-–]+?)\s*[-–]\s*(?:[Кк]оллекци[яи]|[Cc]ollection)`)
|
||||
|
||||
// Label pack title: (Genre) Label: Label Name (releases)
|
||||
labelPackTitlePattern = regexp.MustCompile(`^(?:\([^)]+\)\s*)?(?i)Label:\s*([^(]+)`)
|
||||
|
||||
// Catalog number in brackets: [CAT001], [LABEL-001]
|
||||
catalogNumPattern = regexp.MustCompile(`\[([A-Z]{2,}[-\s]?\d+[A-Z]*)\]`)
|
||||
|
||||
// Tags in brackets at start to strip: [RM], [restored], etc. (before or after genre)
|
||||
leadingTagsPattern = regexp.MustCompile(`^(\s*\[[^\]]+\]\s*)+`)
|
||||
|
||||
// Tags before genre pattern: [RM] [restored] (Genre)
|
||||
tagsBeforeGenrePattern = regexp.MustCompile(`^(\s*\[[^\]]+\]\s*)+\([^)]+\)\s*`)
|
||||
|
||||
// Clean trailing technical info: , FLAC (image+.cue)
|
||||
trailingTechPattern = regexp.MustCompile(`,?\s*(?:FLAC|APE|MP3|AAC|OGG|WV|WavPack|ALAC|WAV).*$`)
|
||||
)
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type SoundtracksParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewSoundtracksParser() *SoundtracksParser {
|
||||
return &SoundtracksParser{}
|
||||
}
|
||||
|
||||
func (p *SoundtracksParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
r.Type = release.TypeSoundtrack
|
||||
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.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
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestSoundtracksParser(t *testing.T) {
|
||||
p := NewSoundtracksParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantAlbum string
|
||||
wantYear int
|
||||
wantFormat release.AudioFormat
|
||||
wantType release.Type
|
||||
wantBitrate string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "Game score MP3",
|
||||
title: "(Score) Yoann Laulan - Cinderia (Original Game Soundtrack) - 2026, MP3, 320 kbps",
|
||||
wantArtist: "Yoann Laulan",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatMP3,
|
||||
wantType: release.TypeSoundtrack,
|
||||
wantBitrate: "320 kbps",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Synthwave game soundtrack",
|
||||
title: "(Synthwave, Dark Synth, Retrowave) VA - Tackle for Loss Official Videogame Soundtrack - 2026, MP3, 320 kbps",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2026,
|
||||
wantType: release.TypeSoundtrack,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Yakuza game OST collection",
|
||||
title: "(Score / Soundtrack) Yakuza Original Soundtracks (39 albums) (SEGA, VA) - 2007-2026, MP3 (tracks), 320 kbps",
|
||||
wantYear: 2007,
|
||||
wantType: release.TypeSoundtrack,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Film score CD FLAC",
|
||||
title: "(Score) [CD] Jonny Greenwood - One Battle After Another (Original Motion Picture Soundtrack) - 2025, FLAC (image+.cue), lossless",
|
||||
wantArtist: "Jonny Greenwood",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeSoundtrack,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "One Piece collection",
|
||||
title: "(Score) VA - One Piece Soundtrack Collection (4 releases) - 2023-2026, MP3 (tracks), 320 kbps",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2023,
|
||||
wantType: release.TypeSoundtrack,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Life is Strange OST collection",
|
||||
title: "(Score/Soundtrack/OST) Jonathan Morali - Life is Strange Collection (8 CD) - 2016-2026, MP3, 320 kbps",
|
||||
wantArtist: "Jonathan Morali",
|
||||
wantYear: 2016,
|
||||
wantType: release.TypeSoundtrack,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "TR24 game soundtrack WEB",
|
||||
title: "[TR24][OF][GM] N.J. Apostol - Routine Original Soundtrack - 2026 (Score), FLAC (tracks), lossless",
|
||||
wantArtist: "N.J. Apostol",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeSoundtrack,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Hans Zimmer F1 film",
|
||||
title: "(Score, Soundtrack) [CD] Hans Zimmer - F1 The Movie - 2025, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "Hans Zimmer",
|
||||
wantYear: 2025,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeSoundtrack,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Stranger Things Netflix",
|
||||
title: "(Soundtrack) [CD] VA - Stranger Things Soundtrack from the Netflix Series Season 5 - 2026, FLAC (tracks+.cue), lossless",
|
||||
wantArtist: "VA",
|
||||
wantYear: 2026,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantType: release.TypeSoundtrack,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Last of Us HBO TR24",
|
||||
title: "[TR24][OF][TV] Gustavo Santaolalla - The Last of Us Soundtrack from HBO Original Series - 2023 (Score/Soundtrack)",
|
||||
wantArtist: "Gustavo Santaolalla",
|
||||
wantYear: 2023,
|
||||
wantType: release.TypeSoundtrack,
|
||||
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 r.Type != release.TypeSoundtrack {
|
||||
t.Errorf("Type = %v, want Soundtrack", r.Type)
|
||||
}
|
||||
|
||||
if tt.wantBitrate != "" && r.Bitrate != tt.wantBitrate {
|
||||
t.Errorf("Bitrate = %q, want %q", r.Bitrate, tt.wantBitrate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package parser
|
||||
|
||||
import "homelab.lan/music-agregator/internal/release"
|
||||
|
||||
type VinylDigitizationParser struct {
|
||||
BaseParser
|
||||
}
|
||||
|
||||
func NewVinylDigitizationParser() *VinylDigitizationParser {
|
||||
return &VinylDigitizationParser{}
|
||||
}
|
||||
|
||||
func (p *VinylDigitizationParser) Parse(title string) *release.Release {
|
||||
r := p.NewRelease(title)
|
||||
|
||||
r.Genres = p.ExtractGenres(title)
|
||||
r.Type = p.DetectType(title)
|
||||
r.Year, r.YearEnd = p.ExtractYearRange(title)
|
||||
r.Format = p.ExtractFormat(title)
|
||||
r.Source = release.SourceVinyl
|
||||
r.RipType = p.ExtractRipType(title)
|
||||
r.Tags = p.ExtractSpecialTags(title)
|
||||
r.Label = p.ExtractLabel(title)
|
||||
r.CatalogNum = p.ExtractCatalogNum(title)
|
||||
r.Artist, r.Album = p.ExtractArtistAlbum(title)
|
||||
r.BitDepth, r.SampleRate = p.ExtractHiRes(title)
|
||||
|
||||
if r.Format == release.FormatUnknown {
|
||||
r.Format = release.FormatFLAC
|
||||
}
|
||||
r.Bitrate = "lossless"
|
||||
|
||||
if condMatch := vinylConditionPattern.FindStringSubmatch(title); len(condMatch) >= 2 {
|
||||
r.Tags = append(r.Tags, "Vinyl:"+condMatch[1])
|
||||
}
|
||||
|
||||
if r.Artist == "" {
|
||||
p.AddError(r, "failed to extract artist")
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"homelab.lan/music-agregator/internal/release"
|
||||
)
|
||||
|
||||
func TestVinylDigitizationParser(t *testing.T) {
|
||||
p := NewVinylDigitizationParser()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
title string
|
||||
wantArtist string
|
||||
wantYear int
|
||||
wantBitDepth int
|
||||
wantSampleRate int
|
||||
wantFormat release.AudioFormat
|
||||
wantRipType string
|
||||
wantParseOK bool
|
||||
}{
|
||||
{
|
||||
name: "standard LP 24/192",
|
||||
title: "(Pop-Rock/Punk) [LP] [24/192] Сектор Газа - Ядрена вошь - 1990, WavPack (image+.cue)",
|
||||
wantArtist: "Сектор Газа",
|
||||
wantYear: 1990,
|
||||
wantBitDepth: 24,
|
||||
wantSampleRate: 192000,
|
||||
wantFormat: release.FormatWavPack,
|
||||
wantRipType: "image+.cue",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "massive vinyl collection",
|
||||
title: "(Synth-Pop) [LP/12''/10''/7''] [24/96] Depeche Mode - The Vinyl Collection (17 Albums, 66 Singles, 6 Compilations, 51 Bootlegs) (429 Releases) - 1981-2024, FLAC (tracks) lossless",
|
||||
wantArtist: "Depeche Mode",
|
||||
wantYear: 1981,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "2xLP 32bit",
|
||||
title: "(Soft Rock, Pop Rock) [2xLP] [32/176.4] Genesis - Turn It On Again - The Hits - 1999(2024,Reissue, 25th anniversary.), WavPack (tracks)",
|
||||
wantArtist: "Genesis",
|
||||
wantYear: 1999,
|
||||
wantBitDepth: 32,
|
||||
wantSampleRate: 176400,
|
||||
wantFormat: release.FormatWavPack,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "32/384 ultra high res",
|
||||
title: "(Prog Rock) [LP] [32/384] Emerson, Lake & Palmer-Emerson, Lake & Palmer - 2025 (1970), WavPack (tracks)",
|
||||
wantYear: 2025,
|
||||
wantBitDepth: 32,
|
||||
wantSampleRate: 384000,
|
||||
wantFormat: release.FormatWavPack,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "soul LP",
|
||||
title: "(Soul, Funk) [LP] [24/192] Curtis Mayfield - Curtis - 1970/2025, FLAC (tracks)",
|
||||
wantArtist: "Curtis Mayfield",
|
||||
wantYear: 1970,
|
||||
wantBitDepth: 24,
|
||||
wantSampleRate: 192000,
|
||||
wantFormat: release.FormatFLAC,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "16/44 standard",
|
||||
title: "(Rock) [LP] [16/44] Tony Sheridan - Collection 4LP - 1976-1987, FLAC (image+.cue)",
|
||||
wantArtist: "Tony Sheridan",
|
||||
wantYear: 1976,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "MFSL pressing",
|
||||
title: "(Rock, Pop Rock) [LP] [24/96] Fleetwood Mac – Mirage - 1982 (1984 MFSL 1-119), FLAC (tracks)",
|
||||
wantArtist: "Fleetwood Mac",
|
||||
wantYear: 1982,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "multiple LPs in one",
|
||||
title: "(Rock) [LP] [24/96] 10cc - 2LP's - 1976, 1977, FLAC (tracks+.cue)",
|
||||
wantArtist: "10cc",
|
||||
wantYear: 1976,
|
||||
wantRipType: "tracks+.cue",
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "collection from vinyl",
|
||||
title: "(Progressive Rock) [LP] [24/192] Marillion, Fish - Vinyl Collection - 1982-1994 (6 releases), FLAC (image+.cue)",
|
||||
wantArtist: "Marillion, Fish",
|
||||
wantYear: 1982,
|
||||
wantParseOK: true,
|
||||
},
|
||||
{
|
||||
name: "Japan vinyl",
|
||||
title: "(Pop) [LP] [24/96] ABBA - The Album (Original Japan Vinyl) - 1977, FLAC (tracks)",
|
||||
wantArtist: "ABBA",
|
||||
wantYear: 1977,
|
||||
wantBitDepth: 24,
|
||||
wantSampleRate: 96000,
|
||||
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.wantBitDepth != 0 && r.BitDepth != tt.wantBitDepth {
|
||||
t.Errorf("BitDepth = %d, want %d", r.BitDepth, tt.wantBitDepth)
|
||||
}
|
||||
|
||||
if tt.wantSampleRate != 0 && r.SampleRate != tt.wantSampleRate {
|
||||
t.Errorf("SampleRate = %d, want %d", r.SampleRate, tt.wantSampleRate)
|
||||
}
|
||||
|
||||
if tt.wantFormat != release.FormatUnknown && r.Format != tt.wantFormat {
|
||||
t.Errorf("Format = %v, want %v", r.Format, tt.wantFormat)
|
||||
}
|
||||
|
||||
if tt.wantRipType != "" && r.RipType != tt.wantRipType {
|
||||
t.Errorf("RipType = %q, want %q", r.RipType, tt.wantRipType)
|
||||
}
|
||||
|
||||
if r.Source != release.SourceVinyl {
|
||||
t.Errorf("Source = %v, want Vinyl", r.Source)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user