-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.cpp
More file actions
executable file
·305 lines (280 loc) · 12.1 KB
/
Copy pathmain.cpp
File metadata and controls
executable file
·305 lines (280 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
#include <QApplication> // вэлике рефакторинг
#include <QMetaType>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QDebug>
#include <QSplashScreen>
#include <QPainter>
#include <QPixmap>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QTimer>
#include <QDialog>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QComboBox>
#include <QPushButton>
#include "mainwindow.h"
#include "cliplayer.h"
#include "playlistswidget.h"
#include "singleinstance.h"
#include "depsmanager.h"
#include "translator.h"
#include <cstdio>
// ---------------------------------------------------------------------------
// Аргументы командной строки:
// zmp [файлы] открыть файлы в очереди воспроизведения
// zmp -o /путь/до/файла то же (открыть с этим файлом)
// zmp --playlist-edit кластер/плейлист add /файл [--force] [--mkplaylist]
// zmp --playlist play кластер/плейлист играть плейлист
// zmp --cli [файлы | --playlist play к/п] консольный режим без GUI
// ---------------------------------------------------------------------------
struct Args {
QStringList openFiles;
// playlist-edit
bool editMode = false;
QString editCluster, editPlaylist, editFile;
bool force = false, mkPlaylist = false;
// playlist play
bool playPlaylist = false;
QString plCluster, plName;
bool cli = false;
};
static void parseArgs(const QStringList &a, Args &out)
{
for (int i = 1; i < a.size(); ++i) {
const QString arg = a.at(i);
if (arg == "-o" || arg == "--open") {
if (++i < a.size()) out.openFiles << a.at(i);
} else if (arg == "--playlist-edit") {
if (++i >= a.size()) break;
const QString spec = a.at(i); // кластер/плейлист
const int slash = spec.indexOf('/');
out.editCluster = slash > 0 ? spec.left(slash) : QStringLiteral("default");
out.editPlaylist = slash > 0 ? spec.mid(slash + 1) : spec;
if (++i < a.size() && a.at(i) == "add") {
if (++i < a.size()) out.editFile = a.at(i);
}
out.editMode = true;
} else if (arg == "--force") {
out.force = true;
} else if (arg == "--mkplaylist") {
out.mkPlaylist = true;
} else if (arg == "--playlist") {
if (++i >= a.size()) break;
const QString verb = a.at(i); // "play"
if (++i >= a.size()) break;
const QString spec = a.at(i); // кластер/плейлист
const int slash = spec.indexOf('/');
out.playPlaylist = (verb == "play");
out.plCluster = slash > 0 ? spec.left(slash) : QStringLiteral("default");
out.plName = slash > 0 ? spec.mid(slash + 1) : spec;
} else if (arg == "--cli") {
out.cli = true;
} else if (!arg.startsWith('-')) {
out.openFiles << arg;
}
}
}
static bool isAudioFile(const QString &path)
{
static const QStringList exts = {"mp3", "wav", "flac", "ogg", "m4a",
"aac", "aiff", "alac", "opus", "wma"};
return exts.contains(QFileInfo(path).suffix().toLower());
}
// zmp --playlist-edit кластер/плейлист add файл [--force] [--mkplaylist]
static int doPlaylistEdit(const Args &args)
{
if (args.editFile.isEmpty()) {
fprintf(stderr, "zmp: --playlist-edit: укажите файл: add /путь/до/файла\n");
return 1;
}
const QFileInfo fi(args.editFile);
if (!fi.exists()) {
fprintf(stderr, "zmp: файл не найден: %s\n", args.editFile.toUtf8().constData());
return 1;
}
if (!args.force && !isAudioFile(args.editFile)) {
fprintf(stderr, "zmp: %s не аудио файл (используйте --force чтобы добавить принудительно)\n",
args.editFile.toUtf8().constData());
return 1;
}
const QString plDir = PlaylistsWidget::clusterPath(args.editCluster)
+ "/" + args.editPlaylist;
if (!QDir(plDir).exists()) {
if (!args.mkPlaylist) {
fprintf(stderr, "zmp: плейлист не существует: %s/%s (добавьте --mkplaylist для создания)\n",
args.editCluster.toUtf8().constData(),
args.editPlaylist.toUtf8().constData());
return 1;
}
QDir().mkpath(plDir);
}
const QString dst = plDir + "/" + fi.fileName();
if (!QFile::copy(args.editFile, dst)) {
fprintf(stderr, "zmp: не удалось добавить файл (возможно, уже есть): %s\n",
dst.toUtf8().constData());
return 1;
}
printf("zmp: добавлено в %s/%s: %s\n",
args.editCluster.toUtf8().constData(),
args.editPlaylist.toUtf8().constData(),
args.editFile.toUtf8().constData());
return 0;
}
// zmp --help | -h
static void printHelp()
{
printf("ZMP - music player\n\n"
"Usage:\n"
" zmp [files...] open files in the play queue\n"
" zmp -o /path/to/file open ZMP with this file in the queue\n"
" zmp --playlist-edit CLUSTER/PLAYLIST add FILE [--force] [--mkplaylist]\n"
" add FILE to playlist (--force: even if not audio,\n"
" --mkplaylist: create playlist/cluster if missing)\n"
" zmp --playlist play CLUSTER/PLAYLIST\n"
" open ZMP playing this playlist\n"
" zmp --cli [files...] console player (no GUI)\n"
" zmp --cli --playlist play CLUSTER/PLAYLIST\n"
" zmp --help | -h show this help\n\n"
"CLI keys: Ctrl+A file manager, Ctrl+B pause, n next, p prev,\n"
" arrows+Enter select track, Ctrl+C exit\n");
}
int main(int argc, char *argv[])
{
for (int i = 1; i < argc; ++i) {
const QString arg = QString::fromLocal8Bit(argv[i]);
if (arg == "--help" || arg == "-h") {
printHelp();
return 0;
}
}
QApplication app(argc, argv);
zmpInstallCrashHandler();
qRegisterMetaType<PlaylistInfo>();
Args args;
parseArgs(app.arguments(), args);
// Режим редактирования плейлиста — без GUI и без единого экземпляра
if (args.editMode) {
Translator::instance().initFromConfigOrLocale();
return doPlaylistEdit(args);
}
// Если экземпляр ZMP уже запущен — передаём ему задачу и выходим
{
QJsonObject msg;
if (args.playPlaylist) {
msg["cmd"] = "playlist";
msg["cluster"] = args.plCluster;
msg["name"] = args.plName;
} else if (!args.openFiles.isEmpty()) {
msg["cmd"] = "open";
QJsonArray arr;
for (const QString &f : args.openFiles)
arr.append(f);
msg["files"] = arr;
}
if (!msg.isEmpty() &&
SingleInstance::sendToRunning(
QJsonDocument(msg).toJson(QJsonDocument::Compact)))
return 0;
}
// Первый запуск: ~/zmp_playlists не существует — диалог выбора языка
// (всегда на английском, не переводится)
if (!QDir(QDir::homePath() + "/zmp_playlists").exists()) {
QDialog dlg;
dlg.setWindowTitle(QStringLiteral("Welcome to ZMP"));
QVBoxLayout *l = new QVBoxLayout(&dlg);
QLabel *title = new QLabel(QStringLiteral("welcome to ZMP"));
QFont tf = title->font();
tf.setBold(true);
tf.setPointSize(tf.pointSize() + 2);
title->setFont(tf);
l->addWidget(title);
l->addWidget(new QLabel(QStringLiteral("select language :")));
QComboBox *langCombo = new QComboBox;
langCombo->addItem(Translator::nativeName(Translator::Russian), int(Translator::Russian));
langCombo->addItem(Translator::nativeName(Translator::English), int(Translator::English));
langCombo->addItem(Translator::nativeName(Translator::German), int(Translator::German));
l->addWidget(langCombo);
QHBoxLayout *btns = new QHBoxLayout;
QPushButton *okBtn = new QPushButton(QStringLiteral("OK"));
QPushButton *exitBtn = new QPushButton(QStringLiteral("EXIT"));
btns->addWidget(okBtn);
btns->addWidget(exitBtn);
l->addLayout(btns);
bool accepted = false;
QObject::connect(okBtn, &QPushButton::clicked, &dlg, [&]() {
Translator::instance().setLanguage(
static_cast<Translator::Language>(langCombo->currentData().toInt()));
accepted = true;
dlg.accept();
});
QObject::connect(exitBtn, &QPushButton::clicked, &dlg, &QDialog::reject);
dlg.exec();
if (!accepted) return 0; // EXIT — закрыть ZMP без сохранения языка
}
// Resolve UI language before any widgets are built
Translator::instance().initFromConfigOrLocale();
// Консольный режим без GUI (пустая очередь допустима — треки можно
// добавить через Ctrl+A файловый менеджер)
if (args.cli) {
QStringList files = args.openFiles;
QString label;
if (files.isEmpty() && args.playPlaylist) {
QDir dir(PlaylistsWidget::clusterPath(args.plCluster) + "/" + args.plName);
label = args.plCluster + "/" + args.plName;
for (const QString &f : dir.entryList(QDir::Files, QDir::Name))
files << dir.absoluteFilePath(f);
}
CliPlayer player;
player.run(files, label);
return app.exec();
}
// Splash "ZMP is starting..." с последними строками логов — показывается
// сразу и закрывается, когда основное окно открыто
QSplashScreen *splash = new QSplashScreen;
splash->setFixedSize(520, 220);
QPixmap pm(520, 220);
pm.fill(QColor("#1b1b1b"));
splash->setPixmap(pm);
splash->showMessage(QStringLiteral("ZMP is starting..."),
Qt::AlignLeft | Qt::AlignTop, Qt::white);
splash->show();
app.processEvents();
MainWindow::setSplash(splash);
MainWindow w;
w.show();
// Основное окно открыто — splash больше не нужен
MainWindow::setSplash(nullptr);
splash->finish(&w);
splash->deleteLater();
// Локальный IPC-сервер: принимаем файлы/плейлисты от вторых экземпляров
SingleInstance ipc;
ipc.listen([&w](const QByteArray &payload) {
const QJsonObject msg = QJsonDocument::fromJson(payload).object();
if (msg.value("cmd").toString() == "open") {
QStringList files;
for (const QJsonValue &v : msg.value("files").toArray())
files << v.toString();
if (!files.isEmpty())
w.openFilesInQueue(files);
} else if (msg.value("cmd").toString() == "playlist") {
w.playExternalPlaylist(msg.value("cluster").toString(),
msg.value("name").toString());
}
});
// Файлы из аргументов запуска (файловый менеджер, -o, --playlist play)
if (!args.openFiles.isEmpty()) {
QTimer::singleShot(200, &w, [&w, files = args.openFiles]() mutable {
w.openFilesInQueue(files);
});
} else if (args.playPlaylist) {
QTimer::singleShot(200, &w, [&w, c = args.plCluster, n = args.plName]() {
w.playExternalPlaylist(c, n);
});
}
return app.exec();
}