Релиз v1.0.0
This commit is contained in:
+29
@@ -0,0 +1,29 @@
|
|||||||
|
# Visual Studio / .NET
|
||||||
|
.vs/
|
||||||
|
**/bin/
|
||||||
|
**/obj/
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Загруженная музыка
|
||||||
|
downloads/*
|
||||||
|
!downloads/.gitkeep
|
||||||
|
|
||||||
|
# Локальные бинарные зависимости
|
||||||
|
tools/*
|
||||||
|
!tools/.gitkeep
|
||||||
|
|
||||||
|
# Состояние QobuzWatchdog
|
||||||
|
data/completed_urls.txt
|
||||||
|
data/failed_urls.txt
|
||||||
|
|
||||||
|
# Streamrip / временные файлы
|
||||||
|
*.db
|
||||||
|
*.tmp
|
||||||
|
*.part
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
Thumbs.db
|
||||||
|
Desktop.ini
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31903.59
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QobuzWatchdog", "QobuzWatchdog\QobuzWatchdog.csproj", "{6E3089C9-7E68-4F3F-B2BC-5DA23BDDED6B}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{6E3089C9-7E68-4F3F-B2BC-5DA23BDDED6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6E3089C9-7E68-4F3F-B2BC-5DA23BDDED6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6E3089C9-7E68-4F3F-B2BC-5DA23BDDED6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6E3089C9-7E68-4F3F-B2BC-5DA23BDDED6B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<startup>
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||||
|
</startup>
|
||||||
|
</configuration>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// Общие сведения об этой сборке предоставляются следующим набором
|
||||||
|
// набора атрибутов. Измените значения этих атрибутов для изменения сведений,
|
||||||
|
// связанные с этой сборкой.
|
||||||
|
[assembly: AssemblyTitle("QobuzWatchdog")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("QobuzWatchdog")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2026")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми
|
||||||
|
// для компонентов COM. Если необходимо обратиться к типу в этой сборке через
|
||||||
|
// из модели COM задайте для атрибута ComVisible этого типа значение true.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// Следующий GUID представляет идентификатор typelib, если этот проект доступен из модели COM
|
||||||
|
[assembly: Guid("6e3089c9-7e68-4f3f-b2bc-5da23bdded6b")]
|
||||||
|
|
||||||
|
// Сведения о версии сборки состоят из указанных ниже четырех значений:
|
||||||
|
//
|
||||||
|
// Основной номер версии
|
||||||
|
// Дополнительный номер версии
|
||||||
|
// Номер сборки
|
||||||
|
// Номер редакции
|
||||||
|
//
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{6E3089C9-7E68-4F3F-B2BC-5DA23BDDED6B}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<RootNamespace>QobuzWatchdog</RootNamespace>
|
||||||
|
<AssemblyName>QobuzWatchdog</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="App.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,328 @@
|
|||||||
|
# Qobuz Streamrip Tools
|
||||||
|
|
||||||
|
Набор утилит для Windows для автоматизации работы со **Streamrip** и загрузками из **Qobuz**.
|
||||||
|
|
||||||
|
Проект объединяет:
|
||||||
|
|
||||||
|
- программу `QobuzWatchdog`, которая последовательно обрабатывает список ссылок через Streamrip и перезапускает зависший процесс;
|
||||||
|
- очистку базы истории Streamrip;
|
||||||
|
- приведение скачанных FLAC-файлов к CD-качеству;
|
||||||
|
- группировку нескольких альбомов одного исполнителя в общую папку.
|
||||||
|
|
||||||
|
> Используйте проект только для контента, который вы имеете право скачивать и обрабатывать, и в соответствии с условиями используемых сервисов.
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```text
|
||||||
|
qobuz-streamrip-tools/
|
||||||
|
├── QobuzWatchdog/
|
||||||
|
│ ├── Properties/
|
||||||
|
│ ├── App.config
|
||||||
|
│ ├── Program.cs
|
||||||
|
│ └── QobuzWatchdog.csproj
|
||||||
|
│
|
||||||
|
├── scripts/
|
||||||
|
│ ├── Clear-StreamripDb.bat
|
||||||
|
│ ├── Convert-FlacToCD.ps1
|
||||||
|
│ └── GroupAlbumsByArtist.ps1
|
||||||
|
│
|
||||||
|
├── tools/
|
||||||
|
│ └── .gitkeep
|
||||||
|
│
|
||||||
|
├── downloads/
|
||||||
|
│ └── .gitkeep
|
||||||
|
│
|
||||||
|
├── data/
|
||||||
|
│ └── urls.txt
|
||||||
|
│
|
||||||
|
├── QobuzWatchdog.sln
|
||||||
|
├── README.md
|
||||||
|
├── LICENSE
|
||||||
|
└── .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
|
## Общий рабочий процесс
|
||||||
|
|
||||||
|
```text
|
||||||
|
data/urls.txt
|
||||||
|
↓
|
||||||
|
QobuzWatchdog
|
||||||
|
↓
|
||||||
|
Streamrip
|
||||||
|
↓
|
||||||
|
downloads/
|
||||||
|
↓
|
||||||
|
Convert-FlacToCD.ps1
|
||||||
|
↓
|
||||||
|
GroupAlbumsByArtist.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Все загрузки Streamrip направляются в папку `downloads` в корне проекта.
|
||||||
|
|
||||||
|
`QobuzWatchdog` при каждом запуске проверяет `%APPDATA%\streamrip\config.toml` и устанавливает параметр `downloads.folder` в путь к локальной папке `downloads`.
|
||||||
|
|
||||||
|
## Скачать все зависимости:
|
||||||
|
|
||||||
|
[Скачать (.zip)](https://cloud.kolbox.duckdns.org/seafhttp/f/f918d0ca3aac4b4195de/?op=view)
|
||||||
|
|
||||||
|
## Основная зависимость — Streamrip
|
||||||
|
|
||||||
|
Streamrip:
|
||||||
|
|
||||||
|
https://github.com/nathom/streamrip
|
||||||
|
|
||||||
|
Для актуальной версии Streamrip необходим Python 3.10 или новее и `pip`.
|
||||||
|
|
||||||
|
Установка:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pip3 install streamrip --upgrade
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверка:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
rip
|
||||||
|
```
|
||||||
|
|
||||||
|
Перед первым использованием необходимо настроить Streamrip и авторизацию Qobuz.
|
||||||
|
|
||||||
|
Для Qobuz требуется подходящая подписка, позволяющая получать соответствующее качество аудио.
|
||||||
|
|
||||||
|
### Где ищется Streamrip
|
||||||
|
|
||||||
|
`QobuzWatchdog` ищет `rip.exe` в следующем порядке:
|
||||||
|
|
||||||
|
1. `tools\rip.exe`;
|
||||||
|
2. системная переменная `PATH`.
|
||||||
|
|
||||||
|
Если Streamrip установлен через `pip`, обычно достаточно, чтобы каталог Python `Scripts` был добавлен в `PATH`.
|
||||||
|
|
||||||
|
## FFmpeg
|
||||||
|
|
||||||
|
Для `Convert-FlacToCD.ps1` необходимы:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ffmpeg.exe
|
||||||
|
ffprobe.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
Сборка FFmpeg для Windows:
|
||||||
|
|
||||||
|
https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z
|
||||||
|
|
||||||
|
После распаковки файлы находятся в каталоге `bin`.
|
||||||
|
|
||||||
|
Их можно положить непосредственно в:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tools/
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт ищет `ffmpeg.exe` и `ffprobe.exe`:
|
||||||
|
|
||||||
|
1. в `tools`;
|
||||||
|
2. затем в системном `PATH`.
|
||||||
|
|
||||||
|
## Зависимости по компонентам
|
||||||
|
|
||||||
|
| Компонент | Streamrip | FFmpeg | FFprobe | PowerShell | .NET Framework |
|
||||||
|
| ------------------------- |:---------:|:------:|:-------:|:----------:|:--------------:|
|
||||||
|
| `QobuzWatchdog` | ✓ | — | — | — | 4.7.2 |
|
||||||
|
| `Clear-StreamripDb.bat` | ✓ | — | — | — | — |
|
||||||
|
| `Convert-FlacToCD.ps1` | — | ✓ | ✓ | 5.1+ | — |
|
||||||
|
| `GroupAlbumsByArtist.ps1` | — | — | — | 5.1+ | — |
|
||||||
|
|
||||||
|
## QobuzWatchdog
|
||||||
|
|
||||||
|
`QobuzWatchdog` читает ссылки из:
|
||||||
|
|
||||||
|
```text
|
||||||
|
data/urls.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждая ссылка должна находиться на отдельной строке.
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://www.qobuz.com/...
|
||||||
|
https://open.qobuz.com/...
|
||||||
|
```
|
||||||
|
|
||||||
|
Строки, начинающиеся с `#`, игнорируются.
|
||||||
|
|
||||||
|
Программа хранит состояние в:
|
||||||
|
|
||||||
|
```text
|
||||||
|
data/completed_urls.txt
|
||||||
|
data/failed_urls.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Эти файлы создаются автоматически и не добавляются в Git.
|
||||||
|
|
||||||
|
### Что делает Watchdog
|
||||||
|
|
||||||
|
Для каждой ссылки программа:
|
||||||
|
|
||||||
|
1. запускает `rip --no-progress --verbose url "<URL>"`;
|
||||||
|
2. отслеживает вывод процесса;
|
||||||
|
3. если Streamrip долго не выводит ничего, завершает дерево процесса;
|
||||||
|
4. повторяет попытку;
|
||||||
|
5. сохраняет успешно обработанные ссылки;
|
||||||
|
6. повторяет незавершённые ссылки в следующих проходах.
|
||||||
|
|
||||||
|
Текущие параметры находятся в `Program.cs`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private const int MaxRunsPerUrl = 10;
|
||||||
|
private const int MaxRounds = 20;
|
||||||
|
private const int IdleLimitSeconds = 60;
|
||||||
|
private const int RestartDelaySeconds = 3;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Сборка QobuzWatchdog
|
||||||
|
|
||||||
|
Проект рассчитан на:
|
||||||
|
|
||||||
|
```text
|
||||||
|
.NET Framework 4.7.2
|
||||||
|
```
|
||||||
|
|
||||||
|
Откройте:
|
||||||
|
|
||||||
|
```text
|
||||||
|
QobuzWatchdog.sln
|
||||||
|
```
|
||||||
|
|
||||||
|
в Visual Studio и соберите конфигурацию `Release`.
|
||||||
|
|
||||||
|
Каталоги `.vs`, `bin` и `obj` исключены из Git.
|
||||||
|
|
||||||
|
## Очистка базы Streamrip
|
||||||
|
|
||||||
|
Запустите:
|
||||||
|
|
||||||
|
```text
|
||||||
|
scripts\Clear-StreamripDb.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт удаляет:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%APPDATA%\streamrip\downloads.db
|
||||||
|
%APPDATA%\streamrip\failed_downloads.db
|
||||||
|
```
|
||||||
|
|
||||||
|
Это сбрасывает историю скачанных и неудачно обработанных элементов Streamrip.
|
||||||
|
|
||||||
|
## Конвертация FLAC в CD-качество
|
||||||
|
|
||||||
|
Запустите:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\scripts\Convert-FlacToCD.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт автоматически обрабатывает:
|
||||||
|
|
||||||
|
```text
|
||||||
|
downloads/
|
||||||
|
```
|
||||||
|
|
||||||
|
Целевые параметры:
|
||||||
|
|
||||||
|
```text
|
||||||
|
FLAC
|
||||||
|
16 бит
|
||||||
|
44,1 кГц
|
||||||
|
2 канала
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт:
|
||||||
|
|
||||||
|
- рекурсивно ищет FLAC;
|
||||||
|
- пропускает уже подходящие файлы;
|
||||||
|
- сохраняет метаданные и встроенные изображения;
|
||||||
|
- создаёт временный файл;
|
||||||
|
- проверяет результат через FFprobe;
|
||||||
|
- только после успешной проверки заменяет исходник;
|
||||||
|
- при ошибке пытается восстановить исходный файл.
|
||||||
|
|
||||||
|
## Группировка альбомов по исполнителям
|
||||||
|
|
||||||
|
Запустите:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\scripts\GroupAlbumsByArtist.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт работает с папкой `downloads`.
|
||||||
|
|
||||||
|
Он ожидает названия альбомов в формате:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Исполнитель - Альбом
|
||||||
|
```
|
||||||
|
|
||||||
|
Если у одного исполнителя найдено два или больше альбомов:
|
||||||
|
|
||||||
|
```text
|
||||||
|
downloads/
|
||||||
|
├── Исполнитель/
|
||||||
|
│ ├── Исполнитель - Альбом 1/
|
||||||
|
│ └── Исполнитель - Альбом 2/
|
||||||
|
```
|
||||||
|
|
||||||
|
Если альбом только один, его папка остаётся на первом уровне.
|
||||||
|
|
||||||
|
## Папка downloads
|
||||||
|
|
||||||
|
Вся музыка находится в:
|
||||||
|
|
||||||
|
```text
|
||||||
|
downloads/
|
||||||
|
```
|
||||||
|
|
||||||
|
Содержимое этой папки исключено из Git:
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
downloads/*
|
||||||
|
!downloads/.gitkeep
|
||||||
|
```
|
||||||
|
|
||||||
|
## Папка tools
|
||||||
|
|
||||||
|
В `tools` можно положить локальные зависимости:
|
||||||
|
|
||||||
|
```text
|
||||||
|
rip.exe
|
||||||
|
ffmpeg.exe
|
||||||
|
ffprobe.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
Бинарные файлы из `tools` не добавляются в Git.
|
||||||
|
|
||||||
|
## PowerShell
|
||||||
|
|
||||||
|
Скрипты рассчитаны на Windows PowerShell 5.1 или новее.
|
||||||
|
|
||||||
|
Если Windows блокирует запуск локальных `.ps1`, проверьте текущую политику:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-ExecutionPolicy
|
||||||
|
```
|
||||||
|
|
||||||
|
При необходимости для текущего пользователя можно использовать:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
|
||||||
|
```
|
||||||
|
|
||||||
|
Меняйте политику выполнения только если понимаете последствия этой настройки.
|
||||||
|
|
||||||
|
## Лицензия
|
||||||
|
|
||||||
|
Исходный код этого репозитория распространяется по лицензии MIT.
|
||||||
|
|
||||||
|
Streamrip и FFmpeg являются отдельными проектами и распространяются на условиях собственных лицензий.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# Каждая ссылка Qobuz должна находиться на отдельной строке.
|
||||||
|
# Строки, начинающиеся с #, игнорируются.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
setlocal
|
||||||
|
|
||||||
|
set "STREAMRIP_DIR=%APPDATA%\streamrip"
|
||||||
|
set "DOWNLOADS_DB=%STREAMRIP_DIR%\downloads.db"
|
||||||
|
set "FAILED_DB=%STREAMRIP_DIR%\failed_downloads.db"
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ============================================
|
||||||
|
echo ОЧИСТКА БАЗЫ STREAMRIP
|
||||||
|
echo ============================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
if exist "%DOWNLOADS_DB%" (
|
||||||
|
del /q "%DOWNLOADS_DB%" >nul 2>&1
|
||||||
|
if exist "%DOWNLOADS_DB%" (
|
||||||
|
echo ОШИБКА: не удалось удалить downloads.db
|
||||||
|
) else (
|
||||||
|
echo downloads.db удалён.
|
||||||
|
)
|
||||||
|
) else (
|
||||||
|
echo downloads.db не найден — пропущено.
|
||||||
|
)
|
||||||
|
|
||||||
|
if exist "%FAILED_DB%" (
|
||||||
|
del /q "%FAILED_DB%" >nul 2>&1
|
||||||
|
if exist "%FAILED_DB%" (
|
||||||
|
echo ОШИБКА: не удалось удалить failed_downloads.db
|
||||||
|
) else (
|
||||||
|
echo failed_downloads.db удалён.
|
||||||
|
)
|
||||||
|
) else (
|
||||||
|
echo failed_downloads.db не найден — пропущено.
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Готово.
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
@@ -0,0 +1,510 @@
|
|||||||
|
#requires -Version 5.1
|
||||||
|
|
||||||
|
<#
|
||||||
|
Конвертация всех FLAC-файлов в CD-качество:
|
||||||
|
16 бит / 44,1 кГц / стерео.
|
||||||
|
|
||||||
|
Скрипт:
|
||||||
|
- рекурсивно ищет все *.flac;
|
||||||
|
- пропускает файлы, которые уже 16 бит / 44100 Гц / стерео;
|
||||||
|
- сохраняет теги и встроенную обложку;
|
||||||
|
- проверяет результат через ffprobe;
|
||||||
|
- после успешной проверки заменяет исходный файл;
|
||||||
|
- показывает прогресс;
|
||||||
|
- в конце выводит список файлов с ошибками.
|
||||||
|
#>
|
||||||
|
|
||||||
|
$ProjectRoot = Split-Path -Parent $PSScriptRoot
|
||||||
|
$ToolsFolder = Join-Path $ProjectRoot "tools"
|
||||||
|
$RootFolder = Join-Path $ProjectRoot "downloads"
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Find-Tool {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$CommandName,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string[]]$LocalNames
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($LocalName in $LocalNames) {
|
||||||
|
$LocalPath = Join-Path $ToolsFolder $LocalName
|
||||||
|
if (Test-Path -LiteralPath $LocalPath -PathType Leaf) {
|
||||||
|
return (Resolve-Path -LiteralPath $LocalPath).Path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$Command = Get-Command $CommandName -ErrorAction SilentlyContinue
|
||||||
|
if ($Command) {
|
||||||
|
return $Command.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$FfmpegPath = Find-Tool -CommandName "ffmpeg.exe" -LocalNames @("ffmpeg.exe")
|
||||||
|
$FfprobePath = Find-Tool -CommandName "ffprobe.exe" -LocalNames @("ffprobe.exe")
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
$Host.UI.RawUI.WindowTitle = "FLAC -> CD quality"
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
}
|
||||||
|
|
||||||
|
function Wait-BeforeExit {
|
||||||
|
Write-Host ""
|
||||||
|
[void](Read-Host "Нажмите Enter для выхода")
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-FlacAudioInfo {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Path
|
||||||
|
)
|
||||||
|
|
||||||
|
$probeOutput = & $FfprobePath `
|
||||||
|
-v error `
|
||||||
|
-select_streams a:0 `
|
||||||
|
-show_entries "stream=codec_name,sample_rate,channels,bits_per_sample,bits_per_raw_sample,sample_fmt" `
|
||||||
|
-of json `
|
||||||
|
-- `
|
||||||
|
$Path 2>&1
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "ffprobe не смог прочитать файл:`n$probeOutput"
|
||||||
|
}
|
||||||
|
|
||||||
|
$probeData = $probeOutput | ConvertFrom-Json
|
||||||
|
|
||||||
|
if (
|
||||||
|
-not $probeData.streams -or
|
||||||
|
$probeData.streams.Count -lt 1
|
||||||
|
) {
|
||||||
|
throw "Аудиопоток не найден."
|
||||||
|
}
|
||||||
|
|
||||||
|
$stream = $probeData.streams[0]
|
||||||
|
$bitDepth = 0
|
||||||
|
|
||||||
|
if (
|
||||||
|
$null -ne $stream.bits_per_raw_sample -and
|
||||||
|
[string]$stream.bits_per_raw_sample -ne "" -and
|
||||||
|
[int]$stream.bits_per_raw_sample -gt 0
|
||||||
|
) {
|
||||||
|
$bitDepth = [int]$stream.bits_per_raw_sample
|
||||||
|
}
|
||||||
|
elseif (
|
||||||
|
$null -ne $stream.bits_per_sample -and
|
||||||
|
[string]$stream.bits_per_sample -ne "" -and
|
||||||
|
[int]$stream.bits_per_sample -gt 0
|
||||||
|
) {
|
||||||
|
$bitDepth = [int]$stream.bits_per_sample
|
||||||
|
}
|
||||||
|
elseif ([string]$stream.sample_fmt -match "s16") {
|
||||||
|
$bitDepth = 16
|
||||||
|
}
|
||||||
|
elseif ([string]$stream.sample_fmt -match "s32") {
|
||||||
|
$bitDepth = 24
|
||||||
|
}
|
||||||
|
|
||||||
|
[PSCustomObject]@{
|
||||||
|
Codec = [string]$stream.codec_name
|
||||||
|
SampleRate = [int]$stream.sample_rate
|
||||||
|
Channels = [int]$stream.channels
|
||||||
|
BitDepth = [int]$bitDepth
|
||||||
|
SampleFmt = [string]$stream.sample_fmt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Clear-Host
|
||||||
|
|
||||||
|
Write-Host "FLAC -> CD quality" -ForegroundColor Cyan
|
||||||
|
Write-Host "16 бит / 44,1 кГц / стерео"
|
||||||
|
Write-Host "Папка: $RootFolder"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
if (
|
||||||
|
-not (
|
||||||
|
Test-Path `
|
||||||
|
-LiteralPath $RootFolder `
|
||||||
|
-PathType Container
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Write-Host "ОШИБКА: папка не найдена:" -ForegroundColor Red
|
||||||
|
Write-Host $RootFolder -ForegroundColor Red
|
||||||
|
|
||||||
|
Wait-BeforeExit
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $FfmpegPath) {
|
||||||
|
Write-Host "ОШИБКА: ffmpeg.exe не найден." -ForegroundColor Red
|
||||||
|
Write-Host "Проверены папка tools и системный PATH."
|
||||||
|
Wait-BeforeExit
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $FfprobePath) {
|
||||||
|
Write-Host "ОШИБКА: ffprobe.exe не найден." -ForegroundColor Red
|
||||||
|
Write-Host "Проверены папка tools и системный PATH."
|
||||||
|
Wait-BeforeExit
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = @(
|
||||||
|
Get-ChildItem `
|
||||||
|
-LiteralPath $RootFolder `
|
||||||
|
-Filter "*.flac" `
|
||||||
|
-File `
|
||||||
|
-Recurse |
|
||||||
|
Where-Object {
|
||||||
|
$_.Name -notlike "*.__cd_temp__.flac"
|
||||||
|
} |
|
||||||
|
Sort-Object FullName
|
||||||
|
)
|
||||||
|
|
||||||
|
$total = $files.Count
|
||||||
|
|
||||||
|
if ($total -eq 0) {
|
||||||
|
Write-Host "FLAC-файлы не найдены." `
|
||||||
|
-ForegroundColor Yellow
|
||||||
|
|
||||||
|
Wait-BeforeExit
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$converted = 0
|
||||||
|
$skipped = 0
|
||||||
|
$failed = 0
|
||||||
|
$index = 0
|
||||||
|
$startTime = Get-Date
|
||||||
|
|
||||||
|
$failedFiles = [System.Collections.Generic.List[object]]::new()
|
||||||
|
|
||||||
|
foreach ($file in $files) {
|
||||||
|
$index++
|
||||||
|
|
||||||
|
$percent = [int][Math]::Floor(
|
||||||
|
(($index - 1) / $total) * 100
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Progress `
|
||||||
|
-Activity "Конвертация FLAC в CD-качество" `
|
||||||
|
-Status (
|
||||||
|
"Файл {0} из {1}: {2}" -f `
|
||||||
|
$index,
|
||||||
|
$total,
|
||||||
|
$file.Name
|
||||||
|
) `
|
||||||
|
-CurrentOperation $file.DirectoryName `
|
||||||
|
-PercentComplete $percent
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
Write-Host (
|
||||||
|
"[{0}/{1}] {2}" -f `
|
||||||
|
$index,
|
||||||
|
$total,
|
||||||
|
$file.FullName
|
||||||
|
) -ForegroundColor White
|
||||||
|
|
||||||
|
$tempFile = Join-Path `
|
||||||
|
-Path $file.DirectoryName `
|
||||||
|
-ChildPath (
|
||||||
|
$file.BaseName +
|
||||||
|
".__cd_temp__.flac"
|
||||||
|
)
|
||||||
|
|
||||||
|
$backupFile = Join-Path `
|
||||||
|
-Path $file.DirectoryName `
|
||||||
|
-ChildPath (
|
||||||
|
$file.Name +
|
||||||
|
".__original_backup__"
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Test-Path -LiteralPath $tempFile) {
|
||||||
|
Remove-Item `
|
||||||
|
-LiteralPath $tempFile `
|
||||||
|
-Force
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $backupFile) {
|
||||||
|
Remove-Item `
|
||||||
|
-LiteralPath $backupFile `
|
||||||
|
-Force
|
||||||
|
}
|
||||||
|
|
||||||
|
$sourceInfo = Get-FlacAudioInfo `
|
||||||
|
-Path $file.FullName
|
||||||
|
|
||||||
|
Write-Host (
|
||||||
|
"Исходник: {0} бит, {1} Гц, {2} канал(а)" -f `
|
||||||
|
$sourceInfo.BitDepth,
|
||||||
|
$sourceInfo.SampleRate,
|
||||||
|
$sourceInfo.Channels
|
||||||
|
)
|
||||||
|
|
||||||
|
$alreadyCdQuality = (
|
||||||
|
$sourceInfo.BitDepth -eq 16 -and
|
||||||
|
$sourceInfo.SampleRate -eq 44100 -and
|
||||||
|
$sourceInfo.Channels -eq 2
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($alreadyCdQuality) {
|
||||||
|
$skipped++
|
||||||
|
|
||||||
|
Write-Host `
|
||||||
|
"Уже CD-качество — пропущено." `
|
||||||
|
-ForegroundColor Green
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Конвертация..." `
|
||||||
|
-ForegroundColor Yellow
|
||||||
|
|
||||||
|
$ffmpegOutput = & $FfmpegPath `
|
||||||
|
-hide_banner `
|
||||||
|
-loglevel error `
|
||||||
|
-y `
|
||||||
|
-i `
|
||||||
|
$file.FullName `
|
||||||
|
-map 0:a:0 `
|
||||||
|
-map "0:v?" `
|
||||||
|
-map_metadata 0 `
|
||||||
|
-c:a flac `
|
||||||
|
-sample_fmt s16 `
|
||||||
|
-ar 44100 `
|
||||||
|
-ac 2 `
|
||||||
|
-compression_level 8 `
|
||||||
|
-c:v copy `
|
||||||
|
-- `
|
||||||
|
$tempFile 2>&1
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "ffmpeg завершился с ошибкой:`n$ffmpegOutput"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
-not (
|
||||||
|
Test-Path `
|
||||||
|
-LiteralPath $tempFile `
|
||||||
|
-PathType Leaf
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw "Временный файл не был создан."
|
||||||
|
}
|
||||||
|
|
||||||
|
$tempFileInfo = Get-Item `
|
||||||
|
-LiteralPath $tempFile
|
||||||
|
|
||||||
|
if ($tempFileInfo.Length -le 0) {
|
||||||
|
throw "Создан пустой временный файл."
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultInfo = Get-FlacAudioInfo `
|
||||||
|
-Path $tempFile
|
||||||
|
|
||||||
|
$validResult = (
|
||||||
|
$resultInfo.BitDepth -eq 16 -and
|
||||||
|
$resultInfo.SampleRate -eq 44100 -and
|
||||||
|
$resultInfo.Channels -eq 2
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not $validResult) {
|
||||||
|
throw (
|
||||||
|
"Проверка результата не пройдена. " +
|
||||||
|
"Получено: {0} бит, {1} Гц, " +
|
||||||
|
"{2} канал(а)." -f `
|
||||||
|
$resultInfo.BitDepth,
|
||||||
|
$resultInfo.SampleRate,
|
||||||
|
$resultInfo.Channels
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
$originalCreationTime = $file.CreationTime
|
||||||
|
$originalLastWriteTime = $file.LastWriteTime
|
||||||
|
|
||||||
|
Move-Item `
|
||||||
|
-LiteralPath $file.FullName `
|
||||||
|
-Destination $backupFile
|
||||||
|
|
||||||
|
try {
|
||||||
|
Move-Item `
|
||||||
|
-LiteralPath $tempFile `
|
||||||
|
-Destination $file.FullName
|
||||||
|
|
||||||
|
$newFile = Get-Item `
|
||||||
|
-LiteralPath $file.FullName
|
||||||
|
|
||||||
|
$newFile.CreationTime = $originalCreationTime
|
||||||
|
$newFile.LastWriteTime = $originalLastWriteTime
|
||||||
|
|
||||||
|
Remove-Item `
|
||||||
|
-LiteralPath $backupFile `
|
||||||
|
-Force
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
if (
|
||||||
|
Test-Path `
|
||||||
|
-LiteralPath $file.FullName
|
||||||
|
) {
|
||||||
|
Remove-Item `
|
||||||
|
-LiteralPath $file.FullName `
|
||||||
|
-Force `
|
||||||
|
-ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Test-Path `
|
||||||
|
-LiteralPath $backupFile
|
||||||
|
) {
|
||||||
|
Move-Item `
|
||||||
|
-LiteralPath $backupFile `
|
||||||
|
-Destination $file.FullName `
|
||||||
|
-ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
|
||||||
|
$converted++
|
||||||
|
|
||||||
|
Write-Host `
|
||||||
|
"Готово — исходный файл заменён." `
|
||||||
|
-ForegroundColor Cyan
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$failed++
|
||||||
|
|
||||||
|
$errorMessage = $_.Exception.Message
|
||||||
|
|
||||||
|
$failedFiles.Add(
|
||||||
|
[PSCustomObject]@{
|
||||||
|
Name = $file.BaseName
|
||||||
|
FileName = $file.Name
|
||||||
|
FullName = $file.FullName
|
||||||
|
Directory = $file.DirectoryName
|
||||||
|
Error = $errorMessage
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host "ОШИБКА:" `
|
||||||
|
-ForegroundColor Red
|
||||||
|
|
||||||
|
Write-Host $errorMessage `
|
||||||
|
-ForegroundColor Red
|
||||||
|
|
||||||
|
if (
|
||||||
|
Test-Path `
|
||||||
|
-LiteralPath $tempFile
|
||||||
|
) {
|
||||||
|
Remove-Item `
|
||||||
|
-LiteralPath $tempFile `
|
||||||
|
-Force `
|
||||||
|
-ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(Test-Path -LiteralPath $backupFile) -and
|
||||||
|
-not (
|
||||||
|
Test-Path `
|
||||||
|
-LiteralPath $file.FullName
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Move-Item `
|
||||||
|
-LiteralPath $backupFile `
|
||||||
|
-Destination $file.FullName `
|
||||||
|
-ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Progress `
|
||||||
|
-Activity "Конвертация FLAC в CD-качество" `
|
||||||
|
-Completed
|
||||||
|
|
||||||
|
$elapsed = (Get-Date) - $startTime
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "========================================" `
|
||||||
|
-ForegroundColor DarkGray
|
||||||
|
|
||||||
|
Write-Host "Обработка завершена" `
|
||||||
|
-ForegroundColor Cyan
|
||||||
|
|
||||||
|
Write-Host (
|
||||||
|
"Всего файлов: {0}" -f $total
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host (
|
||||||
|
"Конвертировано: {0}" -f $converted
|
||||||
|
) -ForegroundColor Cyan
|
||||||
|
|
||||||
|
Write-Host (
|
||||||
|
"Пропущено (уже CD): {0}" -f $skipped
|
||||||
|
) -ForegroundColor Green
|
||||||
|
|
||||||
|
if ($failed -gt 0) {
|
||||||
|
Write-Host (
|
||||||
|
"Ошибок: {0}" -f $failed
|
||||||
|
) -ForegroundColor Red
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Host "Ошибок: 0" `
|
||||||
|
-ForegroundColor Gray
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host (
|
||||||
|
"Время: {0:hh\:mm\:ss}" -f $elapsed
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host "========================================" `
|
||||||
|
-ForegroundColor DarkGray
|
||||||
|
|
||||||
|
if ($failedFiles.Count -gt 0) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "ТРЕКИ С ОШИБКАМИ:" `
|
||||||
|
-ForegroundColor Red
|
||||||
|
|
||||||
|
Write-Host "========================================" `
|
||||||
|
-ForegroundColor DarkGray
|
||||||
|
|
||||||
|
$failedNumber = 0
|
||||||
|
|
||||||
|
foreach ($failedFile in $failedFiles) {
|
||||||
|
$failedNumber++
|
||||||
|
|
||||||
|
Write-Host (
|
||||||
|
"{0}. {1}" -f `
|
||||||
|
$failedNumber,
|
||||||
|
$failedFile.Name
|
||||||
|
) -ForegroundColor Red
|
||||||
|
|
||||||
|
Write-Host (
|
||||||
|
" Файл: {0}" -f `
|
||||||
|
$failedFile.FullName
|
||||||
|
) -ForegroundColor DarkGray
|
||||||
|
|
||||||
|
Write-Host (
|
||||||
|
" Ошибка: {0}" -f `
|
||||||
|
$failedFile.Error
|
||||||
|
) -ForegroundColor DarkYellow
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "========================================" `
|
||||||
|
-ForegroundColor DarkGray
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Все файлы обработаны без ошибок." `
|
||||||
|
-ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
Wait-BeforeExit
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#requires -Version 5.1
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$ProjectRoot = Split-Path -Parent $PSScriptRoot
|
||||||
|
$RootPath = Join-Path $ProjectRoot "downloads"
|
||||||
|
$Separator = " - "
|
||||||
|
|
||||||
|
try {
|
||||||
|
$Host.UI.RawUI.WindowTitle = "Группировка альбомов по исполнителям"
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
}
|
||||||
|
|
||||||
|
function Wait-BeforeExit {
|
||||||
|
Write-Host ""
|
||||||
|
[void](Read-Host "Нажмите Enter для выхода")
|
||||||
|
}
|
||||||
|
|
||||||
|
Clear-Host
|
||||||
|
|
||||||
|
Write-Host "Группировка альбомов по исполнителям" -ForegroundColor Cyan
|
||||||
|
Write-Host "Папка: $RootPath"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $RootPath -PathType Container)) {
|
||||||
|
New-Item -ItemType Directory -Path $RootPath -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$AlbumFolders = Get-ChildItem -LiteralPath $RootPath -Directory |
|
||||||
|
Where-Object {
|
||||||
|
$_.Name.Contains($Separator)
|
||||||
|
} |
|
||||||
|
ForEach-Object {
|
||||||
|
$Artist = ($_.Name -split [regex]::Escape($Separator), 2)[0].Trim()
|
||||||
|
|
||||||
|
[PSCustomObject]@{
|
||||||
|
Artist = $Artist
|
||||||
|
Folder = $_
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$ArtistGroups = $AlbumFolders | Group-Object -Property Artist
|
||||||
|
|
||||||
|
$Moved = 0
|
||||||
|
$Skipped = 0
|
||||||
|
|
||||||
|
foreach ($Group in $ArtistGroups) {
|
||||||
|
# Папка исполнителя создаётся только при наличии двух и более альбомов.
|
||||||
|
if ($Group.Count -lt 2) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$ArtistName = $Group.Name
|
||||||
|
$ArtistPath = Join-Path -Path $RootPath -ChildPath $ArtistName
|
||||||
|
|
||||||
|
if (-not (Test-Path -LiteralPath $ArtistPath -PathType Container)) {
|
||||||
|
New-Item -Path $ArtistPath -ItemType Directory | Out-Null
|
||||||
|
Write-Host "Создана папка: $ArtistName" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($Album in $Group.Group) {
|
||||||
|
$SourcePath = $Album.Folder.FullName
|
||||||
|
$DestinationPath = Join-Path -Path $ArtistPath -ChildPath $Album.Folder.Name
|
||||||
|
|
||||||
|
if (Test-Path -LiteralPath $DestinationPath) {
|
||||||
|
Write-Warning "Папка уже существует, пропущено: $DestinationPath"
|
||||||
|
$Skipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Move-Item `
|
||||||
|
-LiteralPath $SourcePath `
|
||||||
|
-Destination $ArtistPath `
|
||||||
|
-ErrorAction Stop
|
||||||
|
|
||||||
|
Write-Host "Перемещено: $($Album.Folder.Name) -> $ArtistName" -ForegroundColor Cyan
|
||||||
|
$Moved++
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Не удалось переместить '$SourcePath': $($_.Exception.Message)"
|
||||||
|
$Skipped++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Обработка завершена." -ForegroundColor Yellow
|
||||||
|
Write-Host "Перемещено: $Moved"
|
||||||
|
Write-Host "Пропущено: $Skipped"
|
||||||
|
|
||||||
|
Wait-BeforeExit
|
||||||
Reference in New Issue
Block a user