From d0544fc5646eb7d1cd2dc306d3d071287daa55eb Mon Sep 17 00:00:00 2001 From: Maxim Likhachev Date: Tue, 18 Feb 2020 19:38:59 +0300 Subject: [PATCH] =?UTF-8?q?=CE=BC=CE=AD=CE=BB=CE=BF=CF=82=20v2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CoC.md | 7 + README.md | 127 +++++++ melos | 1199 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1333 insertions(+) create mode 100644 CoC.md create mode 100644 README.md create mode 100755 melos diff --git a/CoC.md b/CoC.md new file mode 100644 index 0000000..55df6a7 --- /dev/null +++ b/CoC.md @@ -0,0 +1,7 @@ +# Go Fuck Yourself + +Offended? Go away. We don't need people who are offended because of a stranger on the internet. +Not offended? You're welcome to contribute. + +© Copyright 2015 [WTFCoC Consortium Committee](https://github.com/mniip/wtfcoc). + diff --git a/README.md b/README.md new file mode 100644 index 0000000..89488c5 --- /dev/null +++ b/README.md @@ -0,0 +1,127 @@ +# μέλος + +> +> oo 88888 +> oo 888 +> ooo ooo ooooooo 888 ooooooo ooooooooo +> 888 888 o88 88 88 88 o888 888o o888 888o +> 88oo oo88 o8888 88 88 888 888 888 +> 88ooooooo88 o88 88 88 88 888o o888 888o o88o +> 88 ooo 88 88ooo88 888 8o8 88ooo88 88ooo8888 +> 88 88 +> oo Скрипт для работы с музыкальной коллекцией oo +> + +## Организация коллекции + +``` +/Жанр/Исполнитель [СТРАНА]/Тип альбома/Год - Название/Номер дорожки - Название дорожки. +``` + +Например: + +``` + ├── Music + │ + └── Traditional Doom Metal + │ +    └── Candlemass [SE] + │ +       └── Альбомы + │ + ├── 1986 - Epicus Doomicus Metallicus + │   ├── 01 - Solitude.mp3 + │   ├── 02 - Demon's Gate.mp3 + │   ├── 03 - Crystal Ball.mp3 + │   ├── 04 - Black Stone Wielder.mp3 + │   ├── 05 - Under the Oak.mp3 + │   ├── 06 - A Sorcerer's Pledge.mp3 + │   └── cover.png + │ + ├── 1987 - Nightfall + │   ├── 01 - Gothic Stone.mp3 + │   ├── 02 - The Well of Souls.mp3 + │   ├── 03 - Codex Gigas.mp3 + │   ├── 04 - At the Gallow's End.mp3 + │   ├── 05 - Samarithan.mp3 + │   ├── 06 - Marche Funebre.mp3 + │   ├── 07 - Dark are the Veils of Death.mp3 + │   ├── 08 - Mourner's Lament.mp3 + │   ├── 09 - Bewitched.mp3 + │   ├── 10 - Black Candles.mp3 + │   └── cover.png + │ + └── [...] +``` + +## Использование + +``` + -h --help -Вывод данной справки + -i --info -Вывод статистики + -l --codes -Вывод кодов стран + -noc --nocolor -Отключить цвета + + Поиск данных + + -g --genre -По жанру + -art --artist -По исполнителю + -c --country -По коду страны + -at --type -По типу альбома + -y --year -По дате издания + -an --name -По названию альбома + -tn --track -По номеру дорожки + -tt --title -По названию дорожки + -f --format -По формату (mp3, ogg) + -r --rating -По рейтингу (5, 4,5) + -live --live -По посещённым концертам за указанный год + + -F --love -Треки с рейтингом 5+ + + + Дополнительно + + -P --plain -Вывод без форматирования + -v --verbose -Вывод доп. информации + -all --all -Вывод всех треков + -d --disc -Вывод дискографии + -tl --tlist -Вывод треклистов + -txt --txt -Вывод текстов песен + + -st --stext -Поиск текстов в интернете + -save --save -Сохранение текстов песен + + -go --open -Открыть Web-адрес(а) + + + Работа с плеером + + +p +play -Добавить треки в плейлист + -p -play -Очистить плейлист и добавить треки +``` + +Пример: + +``` +$ melos -g *Doom* -art Saturnus -y 1996 +``` + +Для поиска по нескольким ключевым словам, нужно разделять их запятой: + +``` +$ melos -g Symph,Doom -y 199?,2000,2004 +``` + +Поддерживаются итераторы `*` и `?`. В настройках программы можно +установить использование итераторов по умолчанию. + +Поддерживаются символьные классы `[...]`. Например, опция `-art [A-Z]` отобразит +всех исполнителей, название которых начинается с заглавной латинской буквы. + +В директориях могут находиться файлы с заметками (`info.txt`) и файлы, +содержащие интернет-адрес (`url.txt`), которые будут выведены скриптом на экран. + +# Лицензия + +[GNU General Public License v3.](https://www.gnu.org/licenses/gpl-3.0.html) + diff --git a/melos b/melos new file mode 100755 index 0000000..d6a59fa --- /dev/null +++ b/melos @@ -0,0 +1,1199 @@ +#!/bin/env tclsh + +# +# oo 88888 +# oo 888 +# ooo ooo ooooooo 888 ooooooo ooooooooo +# 888 888 o88 88 88 88 o888 888o o888 888o +# 88oo oo88 o8888 88 88 888 888 888 +# 88ooooooo88 o88 88 88 88 888o o888 888o o88o +# 88 ooo 88 88ooo88 888 8o8 88ooo88 88ooo8888 +# 88 88 +# oo oo +# + +# TODO: Сокращение для академической музыки: - classical/academical -> [0-9]{4}.* +# TODO: Статистика по датам +# TODO: Проверка целостности библиотеки + +# Copyright (C) 2010-2019, Maxim Lihachev, +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published +# by the Free Software Foundation, version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# VERSION +# 2.0 +# +# DESCRIPTION +# +# Директории: +# +# ├── Music +# │ +# └── Traditional Doom Metal +# │ +#    └── Candlemass [SE] +# │ +#       └── Альбомы +# │ +# ├── 1986 - Epicus Doomicus Metallicus +# │   ├── 01 - Solitude.mp3 +# │   ├── 02 - Demon's Gate.mp3 +# │   ├── 03 - Crystal Ball.mp3 +# │   ├── 04 - Black Stone Wielder.mp3 +# │   ├── 05 - Under the Oak.mp3 +# │   ├── 06 - A Sorcerer's Pledge.mp3 +# │   └── cover.png +# │ +# ├── 1987 - Nightfall +# │   ├── 01 - Gothic Stone.mp3 +# │   ├── 02 - The Well of Souls.mp3 +# │   ├── 03 - Codex Gigas.mp3 +# │   ├── 04 - At the Gallow's End.mp3 +# │   ├── 05 - Samarithan.mp3 +# │   ├── 06 - Marche Funebre.mp3 +# │   ├── 07 - Dark are the Veils of Death.mp3 +# │   ├── 08 - Mourner's Lament.mp3 +# │   ├── 09 - Bewitched.mp3 +# │   ├── 10 - Black Candles.mp3 +# │   └── cover.png +# │ +# └── [...] +# +# USAGE +# См. help::usage +# + +package require http + +#------------------------------------------------------------------------------- +# Настройки +#------------------------------------------------------------------------------- +namespace eval opt { + #----------------------------------------------------------------------- + # Настройки музыкальной библиотеки + #----------------------------------------------------------------------- + namespace eval lib { + #Директория с музыкальной коллекцией + set musicDir "/mnt/DATA/Музыка" + + #Файл с информацией + #Номера для сортировки + array set additionalInfo { + 1_tags {"Метки" "tags.txt" line} + 2_url {"Web-страница" "url.txt" url} + 3_notes {"Дополнительно" "info.txt" txt} + 4_concerts {"Посещённые концерты" "concerts.txt" list} + } + + #----- Правила разбора путей: + #Исполнитель: + set reg(1) "^($musicDir)/(.*)/(.*) \\\[(.*)\\\]$" + #Альбом: + set reg(2) "^($musicDir)/(.*)/(.*) \\\[(.*)\\\]/(.*)/(.*?) - (.*)$" + #Дорожка: + set reg(3) "^($musicDir)/(.*)/(.*) \\\[(.*)\\\]/(.*)/(.*?) - (.*)/(.*?)- (.*)\.(\[\\\w\]{3})$" + + #Структура записей в директории + array set path { + Genre "*" + Artist "*" + CountryCode "*" + TypeAlbum "*" + Year "*" + Album "*" + Number "*" + Title "*" + Format "{mp3,ogg,wma,m4a}" + Rating "*" + } + + set concerts "all" + } + + #Коды стран и соответствующие им названия + array set country { + AD "Андорра" + AE "Объединенные Арабские Эмираты" + AF "Афганистан" + AG "Антигуа и Барбуда" + AI "Ангилья" + AL "Албания" + AM "Армения" + AN "Нидерландские Антильские острова" + AO "Ангола" + AQ "Антарктика" + AR "Аргентина" + AS "Американское Самоа" + AT "Австрия" + AU "Австралия" + AW "Аруба" + AZ "Азербайджан" + BA "Босния и Герцеговина" + BB "Барбадос" + BD "Бангладеш" + BE "Бельгия" + BF "Буркина-Фасо " + BG "Болгария" + BH "Бахрейн" + BI "Бурунди" + BJ "Бенин" + BM "Бермудские острова" + BN "Бруней-Даруссалам" + BO "Боливия" + BR "Бразилия" + BS "Багамы" + BT "Бутан" + BV "Буве Остров" + BW "Ботсвана" + BY "Белоруссия" + BZ "Белиз" + CA "Канада" + CC "Кокос (Килинг) острова" + CF "Центрально-Африканская Республика" + CG "Конго" + CH "Швейцария" + CI "Кот-д'Ивуаре" + CK "Острова Кука" + CL "Чили" + CM "Камерун" + CN "Китай" + CO "Колумбии" + CR "Коста-Рика" + CU "Куба" + CV "Кабо-Верде" + CX "Остров Рождества" + CY "Кипр" + CZ "Чехия" + DE "Германия" + DJ "Джибути" + DK "Дания" + DM "Доминика" + DO "Доминиканская Республика" + DZ "Алжир" + EC "Эквадор" + EE "Эстония" + EG "Египет" + EH "Западная Сахара" + ER "Эритрея" + ES "Испания" + ET "Эфиопия" + FI "Финляндия" + FJ "Фиджи" + FK "Фолклендские (Мальвинские) острова" + FM "Микронезия, Федеративные Штаты" + FO "Фарерские острова" + FR "Франция" + FX "Франция, Метрополитен" + GA "Габон" + GB "Соединенное Королевство" + GD "Гренада" + GE "Грузия" + GF "Французская Гвиана" + GH "Гана" + GI "Гибралтар" + GL "Гренландия" + GM "Гамбия" + GN "Гвинея" + GP "Гваделупе" + GQ "Экваториальная Гвинея" + GR "Греция" + GS "Южная Георгия и Южные Сандвичевы острова" + GT "Гватемала" + GU "Гуам" + GW "Гвинея-Бисау" + GY "Гайана" + HK "Гонконг" + HM "Херд и Мак-Дональда Острова" + HN "Гондурас" + HR "Хорватия" + HT "Гаити" + HU "Венгрия" + ID "Индонезия" + IE "Ирландия" + IL "Израиль" + IN "Индия" + IO "Британские территории в Индийском океане" + IQ "Ирак" + IR "Иран" + IS "Исландия" + IT "Италия" + JM "Ямайка" + JO "Иордания" + JP "Япония" + KE "Кения" + KG "Кыргызстан" + KH "Камбоджа" + KI "Кирибати" + KM "Коморские острова" + KN "Сент-Китс и Невис" + KP "Корея, Народно-Демократическая Республика" + KR "Корея" + KW "Кувейт" + KY "Каймановы острова" + KZ "Казахстан" + LA "Лаосская Народно-Демократическая Республика" + LB "Ливан" + LC "Сент-Люсия" + LI "Лихтенштейн" + LK "Шри-Ланка" + LR "Либерия" + LS "Лесото" + LT "Литва" + LU "Люксембург" + LV "Латвия" + LY "Ливийская Арабская Джамахирия" + MA "Марокко" + MC "Монако" + MD "Республика Молдова" + MG "Мадагаскар" + MH "Маршалловы острова" + MK "Македония, Бывшая Югославская Республика" + ML "Мали" + MM "Мьянма" + MN "Монголия" + MO "Макао" + MP "Северные Марианские острова" + MQ "Мартиника" + MR "Мавритания" + MS "Монтсеррат" + MT "Мальта" + MU "Маврикий" + MV "Мальдивы" + MW "Малави" + MX "Мексика" + MY "Малайзия" + MZ "Мозамбик" + NA "Намибия" + NC "Новая Каледония" + NE "Нигера" + NF "Норфолк" + NG "Нигерия" + NI "Никарагуа" + NL "Нидерланды" + NO "Норвегия" + NP "Непал" + NR "Науру" + NU "Ниуэ" + NZ "Новая Зеландия" + OM "Оман" + PA "Панама" + PE "Перу" + PF "Французская Полинезия" + PG "Папуа-Новая Гвинея" + PH "Филиппины" + PK "Пакистан" + PL "Польша" + PM "Сен-Пьер и Микелон" + PN "Питкэрн" + PR "Пуэрто-Рико" + PT "Португалия" + PW "Palau" + PY "Парагвай" + QA "Катар" + RE "Воссоединение" + RO "Румыния" + RU "Российская Федерация" + RW "Руанда" + SA "Саудовская Аравия" + SB "Соломоновы острова" + SC "Сейшельские острова" + SD "Судан" + SE "Швеция" + SG "Сингапур" + SH "Остров Св. Елены" + SI "Словения" + SJ "Шпицберген и Ян-Майен острова" + SK "Словакия" + SL "Сьерра-Леоне" + SM "Сан-Марино" + SN "Сенегал" + SO "Сомали" + SR "Суринам" + SB "Сербия" + ST "Сан-Томе и Принсипи" + SV "Сальвадор" + SY "Сирийская Арабская Республика" + SZ "Свазиленд" + TC "Острова Теркс и Кайкос" + TD "Чада" + TF "Французские Южные территории" + TG "Того" + TH "Таиланд" + TJ "Таджикистан" + TK "Токелау" + TM "Туркменистан" + TN "Тунис" + TO "Тонга" + TP "Восточный Тимор" + TR "Турция" + TT "Тринидад и Тобаго" + TV "Тувалу" + TW "Тайвань" + TZ "Танзания, Объединенная Республика" + UG "Уганда" + UA "Украина" + UK "Соединенное Королевство" + UM "Внешние малые острова" + US "Соединенные Штаты" + UY "Уругвай" + UZ "Узбекистан" + VC "Сент-Винсент и Гренадины" + VE "Венесуэла" + VG "Виргинские острова (Британские)" + VI "Виргинские острова (США)" + VN "Вьетнам" + VU "Фиджи" + WF "Уоллис и Футуна острова" + WS "Самоа" + YE "Йемен" + YT "Майотта" + YU "Югославия" + ZA "Южная Африка" + ZM "Эфиопия" + ZR "Заир" + ZW "Зимбабве" + + VA "Разные страны" + } + + #----------------------------------------------------------------------- + # Настройки плеера + #----------------------------------------------------------------------- + namespace eval player { + #Команды управления плеерами + array set playerCmd { + mpdStart {exec mpd &} + mpdAdd {exec mpc add [regsub "$opt::lib::musicDir/" $Dir {}] &} + mpdClear {exec mpc clear} + + amarok1Start {exec amarok &} + amarok1Add {exec dcop amarok playlist addMedia "$Dir" &} + amarok1Clear {exec dcop amarok playlist clearPlaylist} + + amarok2Start {exec amarok &} + amarok2Add {exec qdbus org.kde.amarok /TrackList AddTrack "$Dir" 1 > /dev/null} + amarok2Clear { + set fTrack [open {| qdbus org.kde.amarok /TrackList GetLength}] + set cTrack [read $fTrack] + + for {set i 0} {$i < $cTrack} {incr i} { + exec qdbus org.kde.amarok /TrackList DelTrack 0 + } + + close $fTrack + } + } + + #Очищать плейлист перед добавлением + set clearPlaylist 0 + #Добавлять найденные песни в плейлист + set addToPlaylist 0 + #Плеер по умолчанию (amarok1, amarok2, mpd) + set defaultPlayer "mpd" + } + + #Версия + set version "2.0 (2020)" + + #Флаг вывода информации на экран. + set fVerbose 1 + #Вывод списка элементов без форматирования + set fPlain 0 + #Вывод дополнительной информации о группе + set fDetails 0 + #Вывод треклистов альбомов. Имеет смысл только при fDisc = 1 + set fTracklist 0 + #1 - выводить дискографии по умолчанию без указания опции -d + set fDisc 0 + #Вывод текстов + set fText 0 + #Концерты + set fLive 0 + + #Открытие ссылок в браузере + set fOpenUrl 0 + #Максимальное количество вкладок для открытия + set maxUrls 10 + #Список ссылок + set urls "" + #Браузер + set browser "firefox-bin" + + #Домен с текстами песен + set lyricSite "http://lyrics.wikia.com" + #Флаг поиска текстов в интернете + set fNetTxt 0 + #Флаг сохранения текстов, загруженных из интернета + set fSave 0 + + # "*" - Все возможные значения при поиске, "" - Искать точное совпадение + set fCw "*" + + #Цветной вывод + set fColor 1 + + #Настройка цвета + array set color { + NC "\033\[00m" + Red "\033\[1;31m" + Green "\033\[1;32m" + Yellow "\033\[1;33m" + Blue "\033\[1;34m" + Magenta "\033\[1;35m" + Cyan "\033\[1;36m" + White "\033\[1;37m" + } + + # Включение опций в пространстве opt + proc on {args} { + foreach o $args {set opt::$o 1} + } + + # Выключение опций в пространстве opt + proc off {args} { + foreach o $args {set opt::$o 0} + } +} + +#------------------------------------------------------------------------------- +# Основные процедуры +#------------------------------------------------------------------------------- +namespace eval app { + #----------------------------------------------------------------------- + # Справка по использованию + #----------------------------------------------------------------------- + namespace eval help { + # Вывод версии + proc version {} { + puts "$app::commandname v$opt::version" + exit 0 + } + + # Вывод справки + proc usage {{exitCode 1}} { + << + << [White] " oo 88888 " [/White] + << [White] " oo 888 " [/White] + << [Cyan] " ooo ooo ooooooo 888 ooooooo ooooooooo " [/Yellow] + << [Cyan] " 888 888 o88 88 88 88 o888 888o o888 888o " [/Yellow] + << [Cyan] " 88oo oo88 o8888 88 88 888 888 888 " [/Cyan] + << [Blue] " 88ooooooo88 o88 88 88 88 888o o888 888o o88o " [/Blue] + << [Blue] " 88 ooo 88 88ooo88 888 8o8 88ooo88 88ooo8888 " [/Blue] + << [Blue] " 88 88 " [/Blue] + << [Blue] " oo Скрипт для работы с музыкальной коллекцией oo " [/Blue] + << + << [Yellow]Организация коллекции[/Yellow] + << + << /Жанр/Исполнитель \[СТРАНА\]/Тип альбома/Год - Название/Номер дорожки - Название дорожки.\n + << + << [Yellow]Использование[/Yellow] $app::commandname \[опции\]: + << + << -h --help -Вывод данной справки + << -i --info -Вывод статистики + << -l --codes -Вывод кодов стран + << -V --version -Версия программы + << -noc --nocolor -Отключить цвета + << + << + << [Yellow]Поиск данных[/Yellow] + << + << -g --genre -По жанру + << -art --artist -По исполнителю + << -c --country -По коду страны + << -at --type -По типу альбома + << -y --year -По дате издания + << -an --name -По названию альбома + << -tn --track -По номеру дорожки + << -tt --title -По названию дорожки + << -f --format -По формату (mp3, ogg) + << -r --rating -По рейтингу (5, 4,5) + << -live --live -По посещённым концертам за указанный год + << + << -F --love -Треки с рейтингом 5+ + << + << + << [Yellow]Дополнительно[/Yellow] + << + << -P --plain -Вывод без форматирования + << -v --verbose -Вывод доп. информации + << -all --all -Вывод всех треков + << -d --disc -Вывод дискографии + << -tl --tlist -Вывод треклистов + << -txt --txt -Вывод текстов песен + << + << -st --stext -Поиск текстов в интернете + << -save --save -Сохранение текстов песен + << + << -go --open -Открыть Web-адрес(а) + << + << + << [Yellow]Работа с плеером[/Yellow] + << + << +p +play -Добавить треки в плейлист + << -p -play -Очистить плейлист и добавить треки + << + << [Yellow]Пример:[/Yellow] $app::commandname -g *Doom* -art Saturnus -y 1996 + << + << > Для поиска по нескольким ключевым словам, нужно разделять их запятой. + << + << [Yellow]Пример:[/Yellow] $app::commandname -g Symph,Doom -y 199?,2000,2004 + << + << > Поддерживаются маски \"*\" и \"?\". В настройках программы можно + << установить использование масок по умолчанию. + << + << > Поддерживаются символьные классы \[...\]. Например, опция -art \[A-Z\] отобразит + << всех исполнителей, название которых начинается с заглавной латинской буквы. + << + << > В директориях могут находиться файлы с заметками (info.txt) и файлы, + << содержащие интернет-адрес (url.txt), которые будут выведены скриптом на экран. + << + + exit $exitCode + } + + # Форматирование справки + proc << {args} { + if {$args eq {}} { + set output {} + } else { + set short [lindex $args 0] + if {[string match {[-+]*} $short]} { + set output [format "\t%-15s%-11s%s" \ + "[Cyan]$short," \ + "[lindex $args 1]" \ + "[/Cyan][join [lrange $args 2 [llength $args]]]"] + } else { + set output " [join $args]" + } + } + puts $output + } + + # Вывод кодов стран + proc countries {} { + set pat [lindex $app::commandline [incr app::argIndex]] + + puts "\n [Magenta]Список стран:\n" + foreach c [lsort [array names opt::country "[string toupper $pat]*"]] { + puts "[Yellow]\t$c[/Yellow] - $opt::country($c)" + } + puts "" + exit 0 + } + #***** + } + + #Запуск поиска + proc Main {flag} { + #Отключение цветного вывода + if {!$opt::fColor} { + foreach color [array names opt::color] { + set opt::color($color) {} + } + } + + #Запуск поиска + if {$flag < 100} { + #Поиск по жанру, исполнителю или стране + library::search [file join \ + $opt::lib::musicDir\ + \{$opt::lib::path(Genre)\} \ + \{$opt::lib::path(Artist)\}\\\[\{$opt::lib::path(CountryCode)\}\\\] + ] 1 + } elseif {$flag < 500} { + #Поиск по типу альбома, названию альбома или году издания + opt::off fDisc + library::search [file join \ + $opt::lib::musicDir \ + \{$opt::lib::path(Genre)\} \ + \{$opt::lib::path(Artist)\}\\\[\{$opt::lib::path(CountryCode)\}\\\] \ + \{$opt::lib::path(TypeAlbum)\} \ + \{$opt::lib::path(Year)\}\{$opt::lib::path(Album)\} + ] 2 + } else { + #Поиск по номеру дорожки или ее названию + opt::off fDisc fTracklist + library::search [file join \ + $opt::lib::musicDir \ + \{$opt::lib::path(Genre)\} \ + \{$opt::lib::path(Artist)\}\\\[\{$opt::lib::path(CountryCode)\}\\\] \ + \{$opt::lib::path(TypeAlbum)\} \ + \{$opt::lib::path(Year)\}\{$opt::lib::path(Album)\} \ + \{$opt::lib::path(Number)\}\{$opt::lib::path(Title)\}.\{$opt::lib::path(Format)\} + ] 3 + } + } + + # Установка опций + proc setArg {arg {val {}}} { + #Установка значения из аргумента или командной строки + if {$val eq ""} { + set val [lindex $app::commandline [expr {$app::argIndex + 1}]] + } else { + set ::opt::lib::$arg $val; return + } + + #Проверка правильности значения аргумента + if {$val ne "" && [lsearch -glob $val "-*"] != 0} { + set ::opt::lib::$arg [ + expr { + $arg != "path(Rating)" \ + ? [regsub -all {(,|$)} $val "$::opt::fCw\\1"] \ + : $val + }] + } else { + puts "\n [Red]Неверное использование аргумента\ + \"[lindex $app::commandline $app::argIndex]\"[/Red]" + help::usage + } + #Переход к следующему аргументу + incr app::argIndex + } + + # Разбор параметров командной строки + proc getOpts {} { + #Разбор аргументов командной строки + for {set app::argIndex 0} {$app::argIndex < $app::commandsCount} {incr app::argIndex} { + switch -- [lindex $app::commandline $app::argIndex] { + -g - --genre { app::setArg path(Genre); incr app::sLevel 1 } + -art - --artist { app::setArg path(Artist); incr app::sLevel 1 } + -c - --country { app::setArg path(CountryCode); incr app::sLevel 1 } + -at - --type { app::setArg path(TypeAlbum); incr app::sLevel 100 } + -y - --year { app::setArg path(Year); incr app::sLevel 200 } + -an - --name { app::setArg path(Album); incr app::sLevel 200 } + -tn - --track { app::setArg path(Number); incr app::sLevel 1000 } + -tt - --title { app::setArg path(Title); incr app::sLevel 1000 } + -f - --format { app::setArg path(Format); incr app::sLevel 1000 } + -r - --rating { app::setArg path(Rating); incr app::sLevel 1000 } + -F - --love { app::setArg path(Rating) "5+"; incr app::sLevel 1000 } + -live - --live { app::setArg concerts;opt::on fDetails fLive; incr app::sLevel 1 } + -all - --all { app::setArg path(Title) "*"; incr app::sLevel 1000 } + +p - +play { opt::on player::addToPlaylist; incr app::sLevel 1000 } + -p - -play { opt::on player::addToPlaylist player::clearPlaylist; incr app::sLevel 1000 } + -txt - --txt { opt::on fText; incr app::sLevel 1000 } + -d - --disc { opt::on fDetails fDisc } + -P - --plain { opt::on fPlain; opt::off fVerbose } + -tl - --tlist { opt::on fDetails fDisc fTracklist } + -st - --stext { opt::on fText fNetTxt; util::setUrlCodes; incr app::sLevel 1000 } + -save - --save { opt::on fSave } + -go - --open { opt::on fOpenUrl } + -v - --verbose { opt::on fDetails } + -i - --info { opt::off fVerbose fPlain; library::statistic } + -noc - --nocolor { opt::off fColor } + -l - --codes { help::countries } + -h - --help { help::usage 0 } + -V - --version { help::version } + + default { + puts "\n [Red]Неизвестный параметр \ + \"[lindex $app::commandline $app::argIndex]\"[/Red]" + help::usage + } + } + } + } + + set artists 0; #Начальное количество исполнителей + set commandline {} + set commandsCount {} + set argIndex 0 + set sLevel 0 +} + +#------------------------------------------------------------------------------- +# Вспомогательные процедуры +#------------------------------------------------------------------------------- +namespace eval util { + # Кодирование адреса + proc encodeUrl {url} { + #Удаление начальных и конечных пробелов, информации в скобках + regsub -all {^\s+|\s+$|\s?(\(.*\)|\[.*\]|\\)} $url {} url + #Замена пробелов на символ подчеркивания + regsub -all {\s+} $url {_} url + #Замена неалфавитных символов на их коды + regsub -all {[^\w]} $url {$::map(&)} url + + return [subst -nocommands $url] + } + + # Создание массива спецсимволов и их кодов + proc setUrlCodes {} { + variable map + for {set i 0} {$i <= 256} {incr i} { + set c [format %c $i] + if {![string match {[a-zA-Z0-9]} $c]} { + set map($c) "%[format %.2x $i]" + } + } + array set map { " " + \n %0d%0a } + } + + # Преобразование кодов HTML в текст + proc html2utf {txt} { + #Расстановка переносов + regsub -all {
} $txt "10;" txt + #Удаление курсива/полужирного начертания/подчеркивания + regsub -all {&#|} $txt {} txt + + #Последовательная замена кодов символами + foreach num [split $txt ";"] { + #Расстановка переводов строк + if {$num ne ""} { append trueTxt [format "%c" $num] } + } + return $trueTxt + } + + # Перевод кодов стран в названия + proc code2country {code} { + regsub -all -- {[A-Z]{2}} $code {$opt::country(\\&) \[\\&\]} code + #\U + regsub -all -- {\\U} $code {U} code + regsub -all -- {-} $code {\/} code + #Возврат преобразованных переменных + subst $code + } + + # + #--------------------- Функции для работы с метаданными файлов ----------------- + # + + # Преобразование числового значения рейтинга в графическое представление + proc rating2stars {rating} { + #Рейтинг не установлен + set stars "---" + + regsub {([1-5])} $rating {[string repeat \u2605 "\1"]} stars + regsub {,5} $stars "\u2606" stars + regsub {\+} $stars "\u2605\u2605\u2605\u2605\u2605" stars + + return [subst $stars] + } + + # Открытие сайтов в браузере + proc openBrowser {} { + #Проверка на максимально допустимое количество адресов + if {[llength $opt::urls] > $opt::maxUrls} { + puts "[Red]Будет открыто много вкладок. Вы уверены? (y/n)[/Red]" + + if {[string tolower [gets stdin]] ne "y"} { return } + } + #Открытие страниц + eval exec $opt::browser $opt::urls & + } +} + +#------------------------------------------------------------------------------- +# Процедуры работы с библиотекой +#------------------------------------------------------------------------------- +namespace eval library { + # Поиск по маске + proc search {searchPath {searchLevel 1}} { + #Сортировка осуществляется по жанрам + foreach directory [lsort [glob -nocomplain -- $searchPath]] { + parsePath $directory $searchLevel + } + + #Подсчет количества различных стран + foreach country [array names opt::country] { + if {$app::countries($country)} { + incr app::cMatch + } + } + + #Вывод информации о количестве совпадений + if {$opt::fVerbose} { foundMatches } + } + + # Разбор пути на компоненты + proc parsePath {searchPath {searchLevel 1}} { + #Разбор путей + if {[regexp -expanded -- $opt::lib::reg($searchLevel) $searchPath match rMatch(Path) \ + rMatch(Жанр) \ + rMatch(Исполнитель) \ + rMatch(КодСтраны) \ + rMatch(Тип) \ + rMatch(Год) \ + rMatch(Название) \ + rMatch(Номер) \ + rMatch(Заголовок) \ + rMatch(Формат)]} { + if {$opt::fLive} { + set pattern [string range $opt::lib::concerts 0 end-1] + set showFile $match/[lindex [array get opt::lib::additionalInfo "*concerts"] {1 1}] + + if {![file exists $showFile]} { + return + } elseif {$pattern ne "*" && $pattern ne "all"} { + set fl [open $showFile] + set cns [read $fl] + close $fl + if {![regexp ".*${pattern}.*" $cns]} { + return + } + } + } + #Перевод кода страны в название + set rMatch(Страна) [util::code2country $rMatch(КодСтраны)] + #Выделение основной страны (указывается первой) + regexp {^[A-Z]{2}} $rMatch(КодСтраны) mainCountryCode + + #Проверка на поиск по рейтингу и вывод результатов + if {$opt::lib::path(Rating) ne "*" && \ + [library::track::readTag $searchPath] ne $opt::lib::path(Rating)} { + return + } elseif {$opt::fVerbose} { + showResults $searchPath + } elseif {$opt::fPlain} { + puts $searchPath + } + #Увеличение количества исполнителей + incr app::countries($mainCountryCode) + incr app::artists + } else { + puts "[Red]Ошибочное имя файла: $searchPath[/Red]" + exit 0 + } + } + + # Вывод результатов поиска + proc showResults {Dir} { + #Создание копии массива результатов поиска + upvar rMatch Data + #Вывод имени исполнителя + puts "\n[Yellow]\u2714 $Data(Исполнитель)[/Yellow]\n" + + #Вывод массива данных + foreach i {Жанр Страна Тип Год Название Номер Заголовок Формат} { + if {$Data($i) ne ""} { + puts "\t[Cyan]$i: [/Cyan]$Data($i)" + } + } + + #Вывод оценки + if {$Data(Формат) eq "mp3"} { + puts "\t[ Cyan]Оценка:\ + [/Cyan][util::rating2stars [library::track::readTag $Dir]]" + #Вывод текста песни + if {$opt::fText} { + library::track::text [regsub -- $Data(Формат) $Dir txt] \ + $Data(Исполнитель) $Data(Заголовок) + } + } + + #Вывод файла с заметкой + if {$opt::fDetails} { + foreach typeInfo [lsort [array names opt::lib::additionalInfo]] { + library::artist::information $Dir $opt::lib::additionalInfo($typeInfo) + } + } + + #Вывод дискографии или треклиста + if {$opt::fDisc} { + library::artist::discography [file join $Data(Path) \ + $Data(Жанр) \ + $Data(Исполнитель)*] + } elseif {$opt::fTracklist} { + library::artist::tracklist [file join $Data(Path) \ + $Data(Жанр) \ + $Data(Исполнитель)* \ + * \ + *$Data(Название)] "\t" + } + + #Добавление песни в плейлист + if {$opt::player::addToPlaylist} { + #Очистка плейлиста + if {$opt::player::clearPlaylist} { + eval $opt::player::playerCmd(${opt::player::defaultPlayer}Clear) + #Сброс флага очистки + set opt::player::clearPlaylist 0 + } + #Добавление песен в плейлист + eval $opt::player::playerCmd(${opt::player::defaultPlayer}Add) + } + } + + # Подсчет совпадений + proc getCount {dir type} { + return [llength [glob -nocomplain -types $type -- $dir]] + } + + # + # Вывод статистики по всем трекам в музыкальной библиотеке. + # + # Пример: + # + # > Жанров: 188 + # > Альбомов: 5688 + # > Песен: 59661 + # > 866 исполнителей из 56 стран: + # + # Люксембург - 1: ▶ + # Мальта - 1: ▶ + # Нидерланды - 22: ■■■■■■■▶ + # Мексика - 3: ■▶ + # Норвегия - 18: ■■■■■■▶ + # Перу - 1: ▶ + # Новая Зеландия - 4: ■▶ + # Польша - 19: ■■■■■■▶ + # Пуэрто-Рико - 1: ▶ + # Португалия - 7: ■■▶ + # Сербия - 6: ■■▶ + # Швеция - 47: ■■■■■■■■■■■■■■■▶ + # Румыния - 1: ▶ + # Словения - 1: ▶ + # Словакия - 4: ■▶ + # [...] + # + proc statistic {} { + #Просмотр дерева директорий + library::search "$opt::lib::musicDir/*/*\\\[*\\\]" 1 + + puts "\n[Yellow]\ + > Жанров: [getCount $opt::lib::musicDir/* d]\n\ + > Альбомов: [getCount $opt::lib::musicDir/*/*/*/* d]\n\ + > Песен: [getCount $opt::lib::musicDir/*/*/*/*/*.$opt::lib::path(Format) f]\n\ + > $app::artists исполнителей из $app::cMatch стран:\n\ + [/Yellow]" + + #Вывод количества исполнителей по странам + foreach c [array names opt::country] { + if {$app::countries($c)} { + puts [format "%-40s %-7s%s" "\t\ + [White]$opt::country($c)[/White]" "- $app::countries($c): "\ + "[string repeat \u25A0 [expr $app::countries($c)/3]]\u25B6"] + } + } + puts "" + exit 0 + } + + # Вывод количества результатов поиска + proc foundMatches {} { + if {$app::artists} { + puts "\n[Yellow]--------------\n\ + \bСовпадений:[White] $app::artists[Yellow]\ + \nСтран: [White] $app::cMatch [/White]\n" + } else { + puts "\n[Red]Ничего не найдено[/Red]\n" + } + + #Открытие браузера + if {$opt::fOpenUrl} { util::openBrowser } + + exit 0 + } + + #----------------------------------------------------------------------- + # Процедуры работы с исполнителем + #----------------------------------------------------------------------- + namespace eval artist { + # Вывод дискографии + proc discography {rootDirectory} { + puts "\n\t[Yellow]Дискография:[/Yellow]" + #Вывод типа альбома (Сборник/ЕР/Live/Альбом/...) + foreach albumType [lsort [glob -nocomplain -types d -- "$rootDirectory*/*"]] { + puts "\n\t\t[Magenta][file tail $albumType]:[/Magenta]\n" + #Вывод дискографии + foreach albumName [lsort [glob -nocomplain \ + -types d \ + -tails \ + -dir $albumType -- "???? - *"]] { + puts -nonewline "\t\t\t[White]$albumName[/White]" + #Вывод треклистов + if {$opt::fTracklist} { + puts ":" + tracklist [file join \ + $rootDirectory* \ + [file tail $albumType] \ + $albumName] \ + "\t\t\t\t" + } + puts "" + } + } + puts "" + } + + # Вывод треклиста + proc tracklist {start sep} { + puts "\n$sep[Yellow]Треклист:[/Yellow]\n" + foreach match [lsort [glob -nocomplain -- "$start*/*.$opt::lib::path(Format)"]] { + puts "$sep\t[file tail $match]" + } + puts "" + } + + # Вывод содержимого текстовых файлов с разбиением на строки + proc information {Dir info} { + lassign $info title file lineType + if {![catch {open $Dir/$file r} inTxt]} { + set text [regsub {\n$} [read $inTxt] {}] + close $inTxt + + if {$lineType eq "txt"} { + puts "\n\t[Cyan]${title}:[/Cyan]\n\n\t\t\ + \b[regsub -all -- {.{75,85} } $text "\&\n\t\t"]" + } elseif {$lineType eq "list"} { + puts "\n[Cyan]\t${title}: [/Cyan]\n" + foreach line [split $text "\n"] { + puts "\t\t$line" + } + } else { + puts "[Cyan]\t${title}: [/Cyan]$text" + + #Добавление адреса сайта в список + if {$lineType eq "url"} {lappend opt::urls $text} + } + } + } + } + + #----------------------------------------------------------------------- + # Процедуры работы с файлом + #----------------------------------------------------------------------- + namespace eval track { + # Извлечение тегов mp3. Tcl-версия + proc readTagTcl {mp3name} { + #Проверка возможности чтения файла + if {[catch {open $mp3name r} fd]} { return } + #Установка двоичного формата файла + fconfigure $fd -encoding binary -translation binary + + if {[catch {seek $fd -128 end}]} { return } + #Чтение файла + set tag [read $fd] + close $fd + binary scan $tag A3 id + + if {$id eq "TAG"} { + binary scan $tag A3A30A30A30A4A28ccc id mp3tag(title) \ + mp3tag(artist) \ + mp3tag(album) \ + mp3tag(year) \ + mp3tag(comment) zero \ + mp3tag(track) \ + mp3tag(genre) + } + puts "TAG" + puts $mp3tag(title) + return $mp3tag(comment) + } + + # Извлечение mp3-тегa Comment + proc readTag {mp3name} { + set rating 0 + + catch {set rating [string trim [exec -- exiftool "$mp3name" | grep Comment | head -n 1 | cut -d : -f 2]]} q w + + return $rating + } + + # Вывод текстов песен + proc text {Dir Artist Title} { + puts "\n\t[Yellow]Текст:[/Yellow]\n" + #Проверка на наличие файла с текстом + if {![catch {open $Dir r} inFile]} { + set output [read $inFile] + close $inFile + } elseif {$opt::fNetTxt} { + #Поиск текста в интернете + set output [searchLyric $Artist $Title] + #Сохранение текста в файле + if {$opt::fSave && $output ne "Текст не найден" \ + && ![catch {open $Dir w} outFile]} { + puts $outFile $output + close $outFile + } + } else { + set output "Текст не найден" + } + #Вывод результатов + foreach i [split $output "\n"] { puts "\t\t$i" } + } + + # Поиск текста в интернете + proc searchLyric {Artist Title} { + #Поиск по wiki + set token [http::geturl "$opt::lyricSite/Special:Search/[util::encodeUrl $Artist]:[util::encodeUrl $Title]"] + upvar #0 $token state + + #Выделение редиректа и загрузка текста + if {[regexp {Location\s(.*?) } $state(meta) match url]} { + set result [loadLyric "$url"] + } elseif {[regexp {.*?mw-search-results.*?href="(/.*?)" title} $state(body) match url]} { + #Если поиск не удался, загрузка первого предложенного варианта + #Не исключены ошибки с загрузкой текста другого исполнителя + set result [loadLyric "$opt::lyricSite$url"] + } else { + set result "Текст не найден" + } + #Закрытие канала + http::cleanup $token + + return $result + } + + # Загрузка текста с lyricwiki.org + proc loadLyric {url} { + #Обращение к адресу + set token [http::geturl [regsub {Special:Search/} $url {}]] + + #Выделение текста + if {[regexp {.*?'lyricbox'>.+div>(&#.*)