1.1.24 - FLAC for HiFi users

This commit is contained in:
exttex 2021-07-25 16:20:11 +02:00
parent 6302f54fbd
commit 53f6b760fe
7 changed files with 93 additions and 23 deletions

View File

@ -148,12 +148,12 @@
"Save": "Zapisz", "Save": "Zapisz",
"Edit": "Edytuj", "Edit": "Edytuj",
"Importer": "Importer", "Importer": "Importer",
"Enter URL": "Enter URL", "Enter URL": "Wprowadź adres URL",
"Currently only Spotify is supported and limited to 100 tracks.": "Currently only Spotify is supported and limited to 100 tracks.", "Currently only Spotify is supported and limited to 100 tracks.": "Obecnie obsługuje tylko Spotify i maksymalnie 100 utworów.",
"Import into playlist": "Import into playlist", "Import into playlist": "Importuj na playlistę",
"Keep sidebar open": "Keep sidebar open", "Keep sidebar open": "Panel boczny zawsze otwarty",
"WARNING: Might require reload to work properly!": "WARNING: Might require reload to work properly!", "WARNING: Might require reload to work properly!": "UWAGA: Może wymagać ponownego uruchomienia, by działać poprawnie!",
"An error occured, URL might be invalid or unsupported.": "An error occured, URL might be invalid or unsupported.", "An error occured, URL might be invalid or unsupported.": "Wystąpił błąd, adres URL może być nieprawidłowy lub nieobsługiwany.",
"Top tracks": "Najpopularniejsze utwory", "Top tracks": "Najpopularniejsze utwory",
"Show all top tracks": "Pokaż wszystkie najpopularniejsze utwory", "Show all top tracks": "Pokaż wszystkie najpopularniejsze utwory",
"Singles": "Single", "Singles": "Single",
@ -163,9 +163,9 @@
"No": "Nie", "No": "Nie",
"Download Filename": "Nazwa pobieranego pliku", "Download Filename": "Nazwa pobieranego pliku",
"Language": "Język", "Language": "Język",
"Background Image": "Background Image", "Background Image": "Obraz w tle",
"Enter URL or absolute path. WARNING: Requires reload!": "Enter URL or absolute path. WARNING: Requires reload!", "Enter URL or absolute path. WARNING: Requires reload!": "Wprowadź adres URL lub całą ścieżkę pliku. UWAGA: Wymaga ponownego uruchomienia!",
"LGBT Mode": "LGBT Mode", "LGBT Mode": "Tryb LGBT",
"Native top bar": "Native top bar", "Native top bar": "Natywny górny pasek",
"Requires restart of Freezer!": "Requires restart of Freezer!" "Requires restart of Freezer!": "Wymaga ponownego uruchomiania Freezera!"
} }

View File

@ -236,7 +236,11 @@ new Vue({
this.playbackInfo = playbackInfo; this.playbackInfo = playbackInfo;
//Stream URL //Stream URL
let url = `${process.env.NODE_ENV === 'development' ? "http://localhost:10069" : window.location.origin}${this.playbackInfo.url}`; let url;
if (this.playbackInfo.encrypted)
url = `${process.env.NODE_ENV === 'development' ? "http://localhost:10069" : window.location.origin}${this.playbackInfo.url}`;
else
url = this.playbackInfo.direct;
//Cancel loading //Cancel loading
this.loaders--; this.loaders--;
if (this.loaders > 0) { if (this.loaders > 0) {
@ -424,7 +428,10 @@ new Vue({
if (this.gapless.promise) resolve(); if (this.gapless.promise) resolve();
} }
this.gapless.info = info this.gapless.info = info
this.gapless.audio = new Audio(`${process.env.NODE_ENV === 'development' ? "http://localhost:10069" : window.location.origin}${info.url}`); if (info.encrypted)
this.gapless.audio = new Audio(`${process.env.NODE_ENV === 'development' ? "http://localhost:10069" : window.location.origin}${info.url}`);
else
this.gapless.audio = new Audio(info.direct);
this.gapless.audio.volume = 0.00; this.gapless.audio.volume = 0.00;
this.gapless.audio.preload = 'auto'; this.gapless.audio.preload = 'auto';
this.gapless.crossfade = false; this.gapless.crossfade = false;

5
app/package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "freezer", "name": "freezer",
"version": "1.1.20", "version": "1.1.23",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "freezer", "name": "freezer",
"version": "1.1.20", "version": "1.1.23",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"arg": "^5.0.0", "arg": "^5.0.0",
@ -18,6 +18,7 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"discord-rpc": "^3.2.0", "discord-rpc": "^3.2.0",
"express": "^4.17.1", "express": "^4.17.1",
"form-data": "^2.3.3",
"lastfmapi": "^0.1.1", "lastfmapi": "^0.1.1",
"metaflac-js2": "^1.0.7", "metaflac-js2": "^1.0.7",
"nedb": "^1.8.0", "nedb": "^1.8.0",

View File

@ -1,7 +1,7 @@
{ {
"name": "freezer", "name": "freezer",
"private": true, "private": true,
"version": "1.1.23", "version": "1.1.24",
"description": "", "description": "",
"main": "background.js", "main": "background.js",
"scripts": { "scripts": {

View File

@ -140,11 +140,37 @@ class DeezerAPI {
this.token = data.results.checkForm; this.token = data.results.checkForm;
this.userId = data.results.USER.USER_ID; this.userId = data.results.USER.USER_ID;
this.favoritesPlaylist = data.results.USER.LOVEDTRACKS_ID.toString(); this.favoritesPlaylist = data.results.USER.LOVEDTRACKS_ID.toString();
try {
await this.authorizeAtv();
} catch (e) {
logger.warn(`Couldnt authorize ATV: ${e}`);
}
if (!this.userId || this.userId == 0 || !this.token) return false; if (!this.userId || this.userId == 0 || !this.token) return false;
return true; return true;
} }
//Authorize Android TV
async authorizeAtv() {
let res = await axios.post(
"https://distribution-api.deezer.com/device/token",
{"brand_name":"Hisense","device_id":"7239e4071d8992c955ad","model_name":"HE50A6109FUWTS","country_code":"FRA"}
);
let deviceToken = res.data.device_token;
// Get unauthorized token
res = await axios.get('https://connect.deezer.com/oauth/access_token.php?grant_type=client_credentials&client_id=447462&client_secret=a83bf7f38ad2f137e444727cfc3775cf&output=json');
let accessToken = res.data.access_token;
// Get smart login code
res = await axios.post(`https://connect.deezer.com/2.0/smartlogin/device?access_token=${accessToken}&device_token=${deviceToken}`);
let smartLoginCode = res.data.data.smartLoginCode;
// Associate
await this.callApi('deezer.associateSmartLoginCodeWithUser', {'SMARTLOGIN_CODE': smartLoginCode});
// Get authorized token
res = await axios.get(`https://connect.deezer.com/2.0/smartlogin/${smartLoginCode}?access_token=${accessToken}`);
accessToken = res.data.data.accessToken;
this.accessToken = accessToken;
}
//Wrapper because electron is piece of shit //Wrapper because electron is piece of shit
async callPublicApi(path, params) { async callPublicApi(path, params) {
if (this.electron) return await this._callPublicApiElectron(path, params); if (this.electron) return await this._callPublicApiElectron(path, params);
@ -223,6 +249,30 @@ class DeezerAPI {
return `https://e-cdns-proxy-${md5origin.substring(0, 1)}.dzcdn.net/mobile/1/${step3}`; return `https://e-cdns-proxy-${md5origin.substring(0, 1)}.dzcdn.net/mobile/1/${step3}`;
} }
//Generate url with android tv support
async generateUrl(trackId, md5origin, mediaVersion, quality = 3) {
if (quality == 9 && this.accessToken) {
try {
let res = await axios({
method: 'GET',
url: `https://api.deezer.com/platform/gcast/track/${trackId}/streamUrls`,
headers: {
"Authorization": "Bearer " + this.accessToken
},
responseType: 'json'
});
if (res.data.data.attributes.url_flac)
return {encrypted: false, url: res.data.data.attributes.url_flac};
} catch (e) {
console.warn(`Failed getting ATV URL! Using normal: ${e}`)
}
}
return {
encrypted: true,
url: DeezerAPI.getUrl(trackId, md5origin, mediaVersion, quality)
};
}
async fallback(info, quality = 3) { async fallback(info, quality = 3) {
let qualityInfo = Track.getUrlInfo(info); let qualityInfo = Track.getUrlInfo(info);
@ -266,11 +316,15 @@ class DeezerAPI {
//Fallback thru available qualities, -1 if none work //Fallback thru available qualities, -1 if none work
async qualityFallback(info, quality = 3) { async qualityFallback(info, quality = 3) {
try { try {
let res = await axios.head(DeezerAPI.getUrl(info.trackId, info.md5origin, info.mediaVersion, quality)); let urlGen = await this.generateUrl(info.trackId, info.md5origin, info.mediaVersion, quality);
let res = await axios.head(urlGen.url);
if (res.status > 400) throw new Error(`Status code: ${res.status}`); if (res.status > 400) throw new Error(`Status code: ${res.status}`);
//Make sure it's an int //Make sure it's an int
info.quality = parseInt(quality.toString(), 10); info.quality = parseInt(quality.toString(), 10);
info.size = parseInt(res.headers['content-length'], 10); info.size = parseInt(res.headers['content-length'], 10);
info.encrypted = urlGen.encrypted;
if (!info.encrypted)
info.direct = urlGen.url;
return info; return info;
} catch (e) { } catch (e) {
logger.warn('Quality fallback: ' + e); logger.warn('Quality fallback: ' + e);

View File

@ -206,6 +206,7 @@ class DownloadThread {
this.stopped = true; this.stopped = true;
this.isUserUploaded = download.track.id.toString().startsWith('-'); this.isUserUploaded = download.track.id.toString().startsWith('-');
this.coverPath = null; this.coverPath = null;
this.encrypted = false;
} }
//Callback wrapper //Callback wrapper
@ -262,9 +263,11 @@ class DownloadThread {
this.download.downloaded = start; this.download.downloaded = start;
//Download //Download
let url = DeezerAPI.getUrl(this.qualityInfo.trackId, this.qualityInfo.md5origin, this.qualityInfo.mediaVersion, this.qualityInfo.quality); let urlGen = await deezer.generateUrl(this.qualityInfo.trackId, this.qualityInfo.md5origin, this.qualityInfo.mediaVersion, this.qualityInfo.quality);
this.encrypted = urlGen.encrypted;
if (this.stopped) return; if (this.stopped) return;
this._request = https.get(url, {headers: {'Range': `bytes=${start}-`}}, (r) => { this._request = https.get(urlGen.url, {headers: {'Range': `bytes=${start}-`}}, (r) => {
this._response = r; this._response = r;
let outFile = fs.createWriteStream(tmp, {flags: 'a'}); let outFile = fs.createWriteStream(tmp, {flags: 'a'});
@ -321,13 +324,18 @@ class DownloadThread {
this.download.state = 2; this.download.state = 2;
//Decrypt //Decrypt
decryptor.decryptFile(decryptor.getKey(this.qualityInfo.trackId), tmp, `${tmp}.DEC`);
let outPath = this.generatePath(this.qualityInfo.quality); let outPath = this.generatePath(this.qualityInfo.quality);
await fs.promises.mkdir(path.dirname(outPath), {recursive: true}); await fs.promises.mkdir(path.dirname(outPath), {recursive: true});
await fs.promises.copyFile(`${tmp}.DEC`, outPath); if (this.encrypted) {
await fs.promises.unlink(`${tmp}.DEC`); decryptor.decryptFile(decryptor.getKey(this.qualityInfo.trackId), tmp, `${tmp}.DEC`);
await fs.promises.copyFile(`${tmp}.DEC`, outPath);
await fs.promises.unlink(`${tmp}.DEC`);
} else {
await fs.promises.copyFile(tmp, outPath);
}
await fs.promises.unlink(tmp); await fs.promises.unlink(tmp);
let cover = null; let cover = null;
if (!this.isUserUploaded) { if (!this.isUserUploaded) {

View File

@ -1,7 +1,7 @@
{ {
"name": "freezer", "name": "freezer",
"private": true, "private": true,
"version": "1.1.23", "version": "1.1.24",
"description": "Freezer PC", "description": "Freezer PC",
"scripts": { "scripts": {
"pack": "electron-builder --dir", "pack": "electron-builder --dir",