Skip to content
This repository has been archived by the owner on Oct 9, 2019. It is now read-only.

Commit

Permalink
Ready for release.
Browse files Browse the repository at this point in the history
  • Loading branch information
superfashi committed Feb 10, 2018
1 parent 4c9e5d3 commit 28b7fb9
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 14 deletions.
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# bp_audio
**bp** stands for **bilibili ported**, but also **bai piao**.

![banner](https://superfashi.b0.upaiyun.com/wp-content/uploads/2018/02/banner-bp-audio.png)

An out-of-the-box extractor for Bilibili's official FLAC music.

It can download Bilibili-purchased music in *SQ* (aka lossless) version without an account whatsoever. While downloading, it can also acquire music's metadata, including album art. It can also download the whole *menu* (aka album) at one time.

# Example
![Example-GIF](https://superfashi.b0.upaiyun.com/wp-content/uploads/2018/02/bp_example.gif)

With metadata included:

![Example-Meta](https://superfashi.b0.upaiyun.com/wp-content/uploads/2018/02/bp_example.png)

Try running this command on your own computer.

# Distribution
You can download the pre-compiled *x86_64 (amd64)* version [here](https://github.com/hanbang-wang/bp_audio/releases), which should be running without any problem on latest Windows devices. Requests for other versions will likely be ignored due to the reason in [License & Disclaimer](#license--disclaimer) section, so you will have to compile it by yourself: see [Compilation](#compilation) section below.

# Compilation
First you'll need *[Qt framework](https://www.qt.io)*; *Qt Creator* is highly suggested.

Next you'll need:

- [gumbo-query](https://github.com/lazytiger/gumbo-query) for HTML DOM parsing.

> Actually I can use regex, but that just makes me an idiot.
- [TagLib](https://github.com/taglib/taglib) for audio file metadata.

Then just `qmake bp_audio.pro` and `make`, as what you would do for every Qt project.

# Usage
```bash
Usage: bp_audio
Get to know some free official FLAC music.

Options:
-a, --au <au> Audio AU number.
--menu Download whole menu.
-o, --out <path> Output folder path.
-?, -h, --help Displays this help.
-v, --version Displays version information.
```

# License & Disclaimer
This little repo is unlicensed, meaning it's released into public domain and you can do anything you want. For more information, please refer to [http://unlicense.org](http://unlicense.org).

This is only a proof-of-concept of how bad Bilibili's inner software and web developers are—the mixed use of params in `path` and `query`, the **useless** key validation of CDN, etc. Therefore, it shall only be used for research and educational purpose.

Neither this project, nor its creator(s), is responsible for any misuses, illegal behaviors, and so forth.
File renamed without changes.
51 changes: 37 additions & 14 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,17 @@ class PictureWrapper {
}
};

const int bar_width = 50;
const QString app_name = "bp_audio";
const QString app_version = "1.0";

QCommandLineOption au_opt({"a", "au"}, "Audio AU number.", "au");
QCommandLineOption all_opt("menu", "Download whole menu.");
QCommandLineOption output_opt({"o", "out"}, "Output folder path.", "path");
QCommandLineParser parser;
QScopedPointer<QNetworkAccessManager> session;
QDir output;
QHash<QString, QSharedPointer<PictureWrapper>> assets;
QHash<QString, QSharedPointer<PictureWrapper> > assets;

QString find_audio(const QByteArray &data) {
CDocument doc;
Expand All @@ -64,6 +68,19 @@ QString find_audio(const QByteArray &data) {
return QString::fromStdString(audio.nodeAt(0).attribute("src"));
}

void download_progress(const qint64 &bytes_received, const qint64 &bytes_total) {
if (bytes_total == -1) return;
std::cerr << '[';
int pos = bar_width * bytes_received / bytes_total;
for (int i = 0; i < bar_width; ++i) {
if (i < pos) std::cerr << '=';
else if (i == pos) std::cerr << '>';
else std::cerr << ' ';
}
std::cerr << "] " << int(100 * bytes_received / bytes_total) << " %\r";
if (bytes_received >= bytes_total) std::cerr << '\n';
}

void write_metadata(const QString &file, const QJsonObject &data,
const int &track, const int &total) {
TagLib::FLAC::File f(TagLib::FileName(file.toStdWString().data()));
Expand Down Expand Up @@ -124,7 +141,7 @@ void download_song(const QJsonObject &data, const int &track = -1, const int &to
QString file_name = QStringLiteral("%1 - %2.flac").arg(data["author"].toString(), data["title"].toString());
std::array<wchar_t, MAX_PATH> buf{};
file_name.toWCharArray(buf.data());
PathCleanupSpec(NULL, buf.data());
PathCleanupSpec(output.absolutePath().toStdWString().data(), buf.data());
file_name = QString::fromWCharArray(buf.data());

const QString &au = QString::number(data["id"].toInt());
Expand Down Expand Up @@ -154,24 +171,27 @@ void download_song(const QJsonObject &data, const int &track = -1, const int &to

QNetworkRequest song_flac(audio_url);
QScopedPointer<QNetworkReply> song_res(session->get(song_flac));
QObject::connect(song_res.data(), &QNetworkReply::downloadProgress, download_progress);
QObject::connect(song_res.data(), &QNetworkReply::readyRead, &loop, &QEventLoop::quit);
QObject::connect(song_res.data(), &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();

if (song_res->error() != QNetworkReply::NoError) {
qCritical("Error loading audio resource");
qDebug(" %s", qUtf8Printable(song_res->errorString()));
std::exit(EXIT_FAILURE);
}

qInfo("Writing to %s...", qUtf8Printable(file_name));
file_name = output.filePath(file_name);
qInfo("Writing to %s...", qUtf8Printable(file_name));
QFile f(file_name);
if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
f.write(song_res->readAll());
do {
loop.exec();
if (song_res->error() != QNetworkReply::NoError) {
qCritical("Error loading audio resource");
qDebug(" %s", qUtf8Printable(song_res->errorString()));
std::exit(EXIT_FAILURE);
}
f.write(song_res->readAll());
} while (!song_res->isFinished());
f.close();
}
write_metadata(file_name, data, track, total);
qInfo("Finished for %s...", qUtf8Printable(file_name));
qInfo("Finished for %s", qUtf8Printable(file_name));
}

void get_menu_info(const QString &menuid) {
Expand Down Expand Up @@ -204,7 +224,7 @@ void get_menu_info(const QString &menuid) {
QString folder_name = QStringLiteral("%1 - %2").arg(menus_response["mbnames"].toString(), menus_response["title"].toString());
std::array<wchar_t, MAX_PATH> buf{};
folder_name.toWCharArray(buf.data());
PathCleanupSpec(NULL, buf.data());
PathCleanupSpec(output.absolutePath().toStdWString().data(), buf.data());
folder_name = QString::fromWCharArray(buf.data());
output.mkdir(folder_name);
output.cd(folder_name);
Expand Down Expand Up @@ -260,14 +280,17 @@ void get_song_info(const QString &au)
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
a.setApplicationName(app_name);
a.setApplicationVersion(app_version);

session.reset(new QNetworkAccessManager());

parser.addOption(au_opt);
parser.addOption(all_opt);
parser.addOption(output_opt);
parser.addHelpOption();
parser.setApplicationDescription("Free bili lossless audio.");
parser.addVersionOption();
parser.setApplicationDescription("Get to know some free official FLAC music.");
parser.process(a);

const QString &au_number = parser.value(au_opt);
Expand Down

0 comments on commit 28b7fb9

Please sign in to comment.