Scan and render mp3 metadata #27
|
@ -1,133 +0,0 @@
|
|||
import { TrackInfo } from "../client";
|
||||
|
||||
export class TrackName extends HTMLElement {
|
||||
container: HTMLElement;
|
||||
|
||||
static get observedAttributes() {
|
||||
return ["name"];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.container = document.createElement("div");
|
||||
}
|
||||
|
||||
get name(): string | null {
|
||||
return this.getAttribute("name");
|
||||
}
|
||||
|
||||
set name(name: string | null) {
|
||||
while (this.container.lastChild) {
|
||||
this.container.removeChild(this.container.lastChild);
|
||||
}
|
||||
if (name) {
|
||||
this.setAttribute("name", name);
|
||||
this.container.appendChild(document.createTextNode(name));
|
||||
} else {
|
||||
this.removeAttribute("name");
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.appendChild(this.container);
|
||||
}
|
||||
}
|
||||
|
||||
export class TrackCard extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ["id", "trackNumber", "name", "album", "artist"];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
attributeChangeCallback(
|
||||
attrName: string,
|
||||
oldValue: string,
|
||||
newValue: string
|
||||
): void {
|
||||
if (newValue !== oldValue) {
|
||||
this.updateContent();
|
||||
}
|
||||
}
|
||||
|
||||
get name(): string | null {
|
||||
return this.getAttribute("name");
|
||||
}
|
||||
|
||||
set name(name: string | null) {
|
||||
if (name) {
|
||||
this.setAttribute("name", name);
|
||||
} else {
|
||||
this.removeAttribute("open");
|
||||
}
|
||||
this.updateContent();
|
||||
}
|
||||
|
||||
get artist(): string | null {
|
||||
return this.getAttribute("artist");
|
||||
}
|
||||
|
||||
set artist(artist: string | null) {
|
||||
if (artist) {
|
||||
this.setAttribute("artist", artist);
|
||||
} else {
|
||||
this.removeAttribute("open");
|
||||
}
|
||||
this.updateContent();
|
||||
}
|
||||
|
||||
get album(): string | null {
|
||||
return this.getAttribute("album");
|
||||
}
|
||||
|
||||
set album(album: string | null) {
|
||||
if (album) {
|
||||
this.setAttribute("album", album);
|
||||
} else {
|
||||
this.removeAttribute("open");
|
||||
}
|
||||
this.updateContent();
|
||||
}
|
||||
|
||||
get length(): string | null {
|
||||
return this.getAttribute("length");
|
||||
}
|
||||
|
||||
set length(length: string | null) {
|
||||
if (length) {
|
||||
this.setAttribute("length", length);
|
||||
} else {
|
||||
this.removeAttribute("open");
|
||||
}
|
||||
this.updateContent();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.updateContent();
|
||||
}
|
||||
|
||||
updateContent() {
|
||||
const container = document.createElement("div");
|
||||
container.classList.add("track-card");
|
||||
this.innerHTML = "";
|
||||
|
||||
this.appendChild(container);
|
||||
|
||||
while (container.lastChild) {
|
||||
container.removeChild(container.lastChild);
|
||||
}
|
||||
|
||||
if (this["name"]) {
|
||||
const trackName = document.createElement("track-name");
|
||||
trackName.name = this["name"];
|
||||
container.appendChild(trackName);
|
||||
}
|
||||
this["length"] && container.appendChild(document.createTextNode("1:23"));
|
||||
this["album"] &&
|
||||
container.appendChild(document.createTextNode("Shatter Me"));
|
||||
this["artist"] &&
|
||||
container.appendChild(document.createTextNode("Lindsey Stirling"));
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
export interface TrackInfo {
|
||||
id: string;
|
||||
track_number?: number;
|
||||
duration?: number;
|
||||
name?: string;
|
||||
album?: string;
|
||||
artist?: string;
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
export class DataCard extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.classList.add("card");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
export class TextField extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ["text"];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
get text(): string | null {
|
||||
return this.getAttribute("text");
|
||||
}
|
||||
|
||||
set text(text: string | null) {
|
||||
if (text) {
|
||||
this.setAttribute("text", text);
|
||||
this.innerHTML = text;
|
||||
} else {
|
||||
this.removeAttribute("text");
|
||||
this.innerHTML = "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
import { TextField } from "./TextField";
|
||||
import { DataCard } from "./DataCard";
|
||||
|
||||
export class TrackCard extends HTMLElement {
|
||||
trackNumberContainer: TextField;
|
||||
nameContainer: TextField;
|
||||
albumContainer: TextField;
|
||||
artistContainer: TextField;
|
||||
durationContainer: TextField;
|
||||
|
||||
static get observedAttributes() {
|
||||
return ["id", "trackNumber", "name", "album", "artist", "duration"];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.trackNumberContainer = document.createElement("text-field");
|
||||
|
||||
this.nameContainer = document.createElement("text-field");
|
||||
this.nameContainer.classList.add("track-card__name");
|
||||
|
||||
this.albumContainer = document.createElement("text-field");
|
||||
this.albumContainer.classList.add("track-card__album");
|
||||
|
||||
this.artistContainer = document.createElement("text-field");
|
||||
this.artistContainer.classList.add("track-card__artist");
|
||||
|
||||
this.durationContainer = document.createElement("text-field");
|
||||
this.durationContainer.classList.add("track-card__duration");
|
||||
}
|
||||
|
||||
get name(): string | null {
|
||||
return this.getAttribute("name");
|
||||
}
|
||||
|
||||
set name(name: string | null) {
|
||||
if (name) {
|
||||
this.setAttribute("name", name);
|
||||
this.nameContainer.text = name;
|
||||
} else {
|
||||
this.removeAttribute("open");
|
||||
this.nameContainer.text = null;
|
||||
}
|
||||
}
|
||||
|
||||
get artist(): string | null {
|
||||
return this.getAttribute("artist");
|
||||
}
|
||||
|
||||
set artist(artist: string | null) {
|
||||
if (artist) {
|
||||
this.setAttribute("artist", artist);
|
||||
this.artistContainer.text = artist;
|
||||
} else {
|
||||
this.removeAttribute("open");
|
||||
this.artistContainer.text = null;
|
||||
}
|
||||
}
|
||||
|
||||
get album(): string | null {
|
||||
return this.getAttribute("album");
|
||||
}
|
||||
|
||||
set album(album: string | null) {
|
||||
if (album) {
|
||||
this.setAttribute("album", album);
|
||||
this.albumContainer.text = album;
|
||||
} else {
|
||||
this.removeAttribute("open");
|
||||
this.albumContainer.text = null;
|
||||
}
|
||||
}
|
||||
|
||||
get duration(): string | null {
|
||||
return this.getAttribute("duration");
|
||||
}
|
||||
|
||||
set duration(duration: string | null) {
|
||||
if (duration) {
|
||||
this.setAttribute("duration", duration);
|
||||
this.durationContainer.text = duration;
|
||||
} else {
|
||||
this.removeAttribute("open");
|
||||
this.durationContainer.text = null;
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
const container = document.createElement("data-card");
|
||||
container.classList.add("track-card");
|
||||
|
||||
// this.appendChild(this.trackNumberContainer);
|
||||
container.appendChild(this.nameContainer);
|
||||
container.appendChild(this.albumContainer);
|
||||
container.appendChild(this.artistContainer);
|
||||
container.appendChild(this.durationContainer);
|
||||
|
||||
this.appendChild(container);
|
||||
}
|
||||
}
|
|
@ -1,30 +1,30 @@
|
|||
import * as _ from "lodash";
|
||||
import { TrackInfo, getTracks } from "./client";
|
||||
import { TrackName, TrackCard } from "./blocks/track";
|
||||
import { DataCard } from "./components/DataCard";
|
||||
import { TextField } from "./components/TextField";
|
||||
import { TrackCard } from "./components/TrackCard";
|
||||
|
||||
window.customElements.define("track-name", TrackName);
|
||||
window.customElements.define("data-card", DataCard);
|
||||
window.customElements.define("text-field", TextField);
|
||||
window.customElements.define("track-card", TrackCard);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"track-name": TrackName;
|
||||
"data-card": DataCard;
|
||||
"text-field": TextField;
|
||||
"track-card": TrackCard;
|
||||
}
|
||||
}
|
||||
|
||||
const replaceTitle = () => {
|
||||
const title = document.querySelector(".js-title");
|
||||
if (title && title.innerHTML) {
|
||||
title.innerHTML = "Music Player Paused";
|
||||
}
|
||||
};
|
||||
|
||||
const updateTrackList = (tracks: TrackInfo[]) => {
|
||||
const track_list = document.querySelector(".track-list__tracks");
|
||||
if (track_list) {
|
||||
let track_formats = _.map(tracks, (info) => {
|
||||
let card: TrackCard = document.createElement("track-card");
|
||||
card.name = info.name || null;
|
||||
card.album = info.album || null;
|
||||
card.artist = info.artist || null;
|
||||
card.duration = (info.duration && `${info.duration}`) || null;
|
||||
return card;
|
||||
});
|
||||
_.map(track_formats, (trackCard) => {
|
||||
|
|
|
@ -29,17 +29,7 @@ body {
|
|||
margin-top: 32px;
|
||||
}
|
||||
|
||||
/*
|
||||
.track-list__row:nth-child(even) {
|
||||
background-color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.track-list__row:nth-child(odd) {
|
||||
background-color: rgb(200, 200, 200);
|
||||
}
|
||||
*/
|
||||
|
||||
.track-card {
|
||||
.card {
|
||||
border: 1px solid black;
|
||||
border-radius: 5px;
|
||||
padding: 8px;
|
||||
|
@ -47,15 +37,32 @@ body {
|
|||
height: 100px;
|
||||
}
|
||||
|
||||
.track-card {
|
||||
display: grid;
|
||||
gap: 4px 4px;
|
||||
}
|
||||
|
||||
.track-card__name {
|
||||
display: block;
|
||||
grid-column: 1 / span 1;
|
||||
grid-row: 1 / span 1;
|
||||
}
|
||||
|
||||
.track-card__length {
|
||||
display: block;
|
||||
grid-column: 1 / span 1;
|
||||
grid-row: 2 / span 1;
|
||||
}
|
||||
|
||||
.track-card__album {
|
||||
display: block;
|
||||
grid-column: 2 / span 1;
|
||||
grid-row: 1 / span 1;
|
||||
}
|
||||
|
||||
.track-card__artist {
|
||||
display: block;
|
||||
grid-column: 2 / span 1;
|
||||
grid-row: 2 / span 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
|
@ -77,6 +83,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
|
@ -87,63 +102,6 @@ dependencies = [
|
|||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote 1.0.23",
|
||||
"strsim",
|
||||
"syn 1.0.107",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dbus"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libdbus-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_is_enum_variant"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0ac8859845146979953797f03cc5b282fb4396891807cdb3d04929a88418197"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"quote 0.3.15",
|
||||
"syn 0.11.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.6"
|
||||
|
@ -154,17 +112,6 @@ dependencies = [
|
|||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-kinds"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e40a16955681d469ab3da85aaa6b42ff656b3c67b52e1d8d3dd36afe97fd462"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.2.0"
|
||||
|
@ -186,6 +133,16 @@ dependencies = [
|
|||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flow"
|
||||
version = "0.1.0"
|
||||
|
@ -205,27 +162,6 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "from_variants"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd6e0c314d32a210f213d2de7fc205a415498c32477022bffdb8927a95f44493"
|
||||
dependencies = [
|
||||
"from_variants_impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "from_variants_impl"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "243da8644e4e4ac28a22f7f30187f9d3f17eafbc219884ff8935edb5ebfe621c"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.25"
|
||||
|
@ -351,15 +287,6 @@ dependencies = [
|
|||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.2.6"
|
||||
|
@ -428,10 +355,15 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
name = "id3"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
checksum = "19d7a833474b30425eb64132d1f9b727b4e39537418bcc3288497c8d2f5c8948"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"flate2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
|
@ -474,15 +406,6 @@ version = "0.2.139"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2264f9d90a9b4e60a2dc722ad899ea0374f03c2e96e755fe22a8f551d4d5fb3c"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.25.2"
|
||||
|
@ -534,6 +457,15 @@ dependencies = [
|
|||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.5"
|
||||
|
@ -546,19 +478,6 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mpris"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e3d377fd27d9d5c7145341cd3affcb83839c24c73e7460488b3ae0a3f9c5166"
|
||||
dependencies = [
|
||||
"dbus",
|
||||
"derive_is_enum_variant",
|
||||
"enum-kinds",
|
||||
"from_variants",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multipart"
|
||||
version = "0.18.0"
|
||||
|
@ -581,10 +500,10 @@ dependencies = [
|
|||
name = "music-player"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dbus",
|
||||
"flow",
|
||||
"id3",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"mpris",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"thiserror",
|
||||
|
@ -655,8 +574,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -698,12 +617,6 @@ version = "1.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.23"
|
||||
|
@ -824,8 +737,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -907,23 +820,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
|
||||
dependencies = [
|
||||
"quote 0.3.15",
|
||||
"synom",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.107"
|
||||
|
@ -931,19 +827,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.23",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synom"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
|
@ -974,8 +861,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1020,8 +907,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.23",
|
||||
"syn 1.0.107",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1158,18 +1045,6 @@ dependencies = [
|
|||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.3.1"
|
||||
|
|
|
@ -6,10 +6,10 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dbus = { version = "0.9.7" }
|
||||
flow = { path = "../../flow" }
|
||||
mime_guess = "2.0.4"
|
||||
mpris = { version = "2.0" }
|
||||
id3 = { version = "1.6" }
|
||||
mime_guess = { version = "2.0" }
|
||||
mime = { version = "0.3" }
|
||||
rusqlite = { version = "0.28" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = { version = "1.0" }
|
||||
|
|
|
@ -7,10 +7,10 @@ use std::{
|
|||
use warp::Filter;
|
||||
|
||||
use music_player::{
|
||||
audio::TrackInfo,
|
||||
core::Core,
|
||||
database::{MemoryIndex, MusicIndex},
|
||||
music_scanner::FileScanner,
|
||||
media::TrackInfo,
|
||||
scanner::FileScanner,
|
||||
};
|
||||
|
||||
fn tracks(index: &Arc<impl MusicIndex>) -> Vec<TrackInfo> {
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use crate::{
|
||||
audio::TrackInfo, database::MusicIndex, music_scanner::MusicScanner, Error, FatalError,
|
||||
};
|
||||
use crate::{database::MusicIndex, media::TrackInfo, scanner::MusicScanner, Error, FatalError};
|
||||
use flow::{ok, Flow};
|
||||
use std::{
|
||||
sync::{
|
||||
|
@ -108,7 +106,7 @@ impl Core {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{audio::TrackId, database::MemoryIndex, music_scanner::factories::MockScanner};
|
||||
use crate::{database::MemoryIndex, media::TrackId, scanner::factories::MockScanner};
|
||||
use std::collections::HashSet;
|
||||
|
||||
fn with_example_index<F>(f: F)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
audio::{TrackId, TrackInfo},
|
||||
media::{TrackId, TrackInfo},
|
||||
FatalError,
|
||||
};
|
||||
use flow::{error, ok, Flow};
|
||||
|
@ -129,6 +129,8 @@ mod test {
|
|||
name: None,
|
||||
album: None,
|
||||
artist: None,
|
||||
duration: None,
|
||||
filetype: "text/plain".parse::<mime::Mime>().unwrap(),
|
||||
};
|
||||
index.add_track(info.clone());
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
pub mod audio;
|
||||
pub mod core;
|
||||
pub mod database;
|
||||
pub mod music_scanner;
|
||||
pub mod media;
|
||||
pub mod scanner;
|
||||
use database::DatabaseError;
|
||||
use thiserror::Error;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ Luminescent Dreams Tools is distributed in the hope that it will be useful, but
|
|||
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use serde::Serialize;
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -46,28 +46,10 @@ pub enum AudioError {
|
|||
#[error("Play was ordered, but nothing is in the queue")]
|
||||
NothingInQueue,
|
||||
|
||||
#[error("Unknown dbus error")]
|
||||
DbusError(dbus::Error),
|
||||
|
||||
#[error("Unknown problem with mpris")]
|
||||
MprisError(mpris::DBusError),
|
||||
|
||||
#[error("url parse error {0}")]
|
||||
UrlError(url::ParseError),
|
||||
}
|
||||
|
||||
impl From<dbus::Error> for AudioError {
|
||||
fn from(err: dbus::Error) -> Self {
|
||||
Self::DbusError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpris::DBusError> for AudioError {
|
||||
fn from(err: mpris::DBusError) -> Self {
|
||||
Self::MprisError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<url::ParseError> for AudioError {
|
||||
fn from(err: url::ParseError) -> Self {
|
||||
Self::UrlError(err)
|
||||
|
@ -102,6 +84,16 @@ pub struct TrackInfo {
|
|||
pub name: Option<String>,
|
||||
pub album: Option<String>,
|
||||
pub artist: Option<String>,
|
||||
pub duration: Option<u32>,
|
||||
#[serde(serialize_with = "serialize_mime")]
|
||||
pub filetype: mime::Mime,
|
||||
}
|
||||
|
||||
fn serialize_mime<S>(val: &mime::Mime, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
s.serialize_str(val.essence_str())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
|
@ -1,16 +1,19 @@
|
|||
use crate::audio::{TrackId, TrackInfo};
|
||||
use crate::media::{TrackId, TrackInfo};
|
||||
use id3::{Tag, TagLike};
|
||||
use std::{
|
||||
fs::{DirEntry, ReadDir},
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ScannerError {
|
||||
#[error("Cannot scan {0}")]
|
||||
CannotScan(PathBuf),
|
||||
#[error("Not found {0}")]
|
||||
NotFound(PathBuf),
|
||||
#[error("Cannot scan file")]
|
||||
CannotScan,
|
||||
#[error("File not found")]
|
||||
FileNotFound,
|
||||
#[error("Tag not found")]
|
||||
TagNotFound,
|
||||
#[error("IO error {0}")]
|
||||
IO(std::io::Error),
|
||||
}
|
||||
|
@ -21,6 +24,19 @@ impl From<std::io::Error> for ScannerError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<id3::Error> for ScannerError {
|
||||
fn from(err: id3::Error) -> ScannerError {
|
||||
match err.kind {
|
||||
id3::ErrorKind::Io(err) => ScannerError::IO(err),
|
||||
id3::ErrorKind::StringDecoding(_) => ScannerError::CannotScan,
|
||||
id3::ErrorKind::NoTag => ScannerError::TagNotFound,
|
||||
id3::ErrorKind::Parsing => ScannerError::CannotScan,
|
||||
id3::ErrorKind::InvalidInput => ScannerError::CannotScan,
|
||||
id3::ErrorKind::UnsupportedFeature => ScannerError::CannotScan,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MusicScanner: Send {
|
||||
fn scan<'a>(&'a self) -> Box<dyn Iterator<Item = Result<TrackInfo, ScannerError>> + 'a>;
|
||||
}
|
||||
|
@ -42,15 +58,62 @@ pub struct FileIterator {
|
|||
|
||||
impl FileIterator {
|
||||
fn scan_file(&self, path: PathBuf) -> Result<TrackInfo, ScannerError> {
|
||||
println!(
|
||||
"[{:?}] {}",
|
||||
mime_guess::from_path(path.clone()).first(),
|
||||
path.to_str().unwrap()
|
||||
);
|
||||
let mimetype = mime_guess::from_path(path.clone())
|
||||
.first()
|
||||
.ok_or(ScannerError::CannotScan)?;
|
||||
match (mimetype.type_(), mimetype.subtype().as_str()) {
|
||||
(mime::AUDIO, "mpeg") => TrackInfo::scan_id3(path, mimetype),
|
||||
|
||||
/*
|
||||
(mime::AUDIO, "ogg") => Ok(TrackInfo {
|
||||
id: TrackId::from(path.to_str().unwrap().to_owned()),
|
||||
album: None,
|
||||
artist: None,
|
||||
name: path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.map(|s| s.to_owned()),
|
||||
track_number: None,
|
||||
filetype: mimetype,
|
||||
}),
|
||||
(mime::AUDIO, "flac") => Ok(TrackInfo {
|
||||
id: TrackId::from(path.to_str().unwrap().to_owned()),
|
||||
album: None,
|
||||
artist: None,
|
||||
name: path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.map(|s| s.to_owned()),
|
||||
track_number: None,
|
||||
filetype: mimetype,
|
||||
}),
|
||||
*/
|
||||
_ => Err(ScannerError::CannotScan),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TrackInfo {
|
||||
fn scan_id3(path: PathBuf, mimetype: mime::Mime) -> Result<TrackInfo, ScannerError> {
|
||||
let tags = Tag::read_from_path(path.clone()).map_err(ScannerError::from)?;
|
||||
|
||||
Ok(TrackInfo {
|
||||
id: TrackId::from(path.to_str().unwrap().to_owned()),
|
||||
album: None,
|
||||
artist: None,
|
||||
name: path
|
||||
.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.map(|s| s.to_owned()),
|
||||
album: tags.album().map(|s| s.to_owned()),
|
||||
artist: tags.artist().map(|s| s.to_owned()),
|
||||
name: tags.title().map(|s| s.to_owned()).or_else(|| {
|
||||
path.file_stem()
|
||||
.and_then(|s| s.to_str())
|
||||
.map(|s| s.to_owned())
|
||||
}),
|
||||
duration: tags.duration(),
|
||||
track_number: None,
|
||||
filetype: mimetype,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +163,7 @@ impl Iterator for FileIterator {
|
|||
}
|
||||
Err(err) => {
|
||||
if err.kind() == std::io::ErrorKind::NotFound {
|
||||
Some(Err(ScannerError::NotFound(dir)))
|
||||
Some(Err(ScannerError::FileNotFound))
|
||||
} else {
|
||||
Some(Err(ScannerError::from(err)))
|
||||
}
|
||||
|
@ -124,7 +187,7 @@ impl MusicScanner for FileScanner {
|
|||
#[cfg(test)]
|
||||
pub mod factories {
|
||||
use super::*;
|
||||
use crate::audio::TrackId;
|
||||
use crate::media::TrackId;
|
||||
|
||||
pub struct MockScanner {
|
||||
data: Vec<TrackInfo>,
|
||||
|
@ -140,6 +203,8 @@ pub mod factories {
|
|||
name: Some("Track 1".to_owned()),
|
||||
album: Some("Savanni's Demo".to_owned()),
|
||||
artist: Some("Savanni".to_owned()),
|
||||
duration: Some(15),
|
||||
filetype: "audio/mpeg".parse::<mime::Mime>().unwrap(),
|
||||
},
|
||||
TrackInfo {
|
||||
id: TrackId::from("/home/savanni/Track 2.mp3".to_owned()),
|
||||
|
@ -147,6 +212,8 @@ pub mod factories {
|
|||
name: Some("Track 2".to_owned()),
|
||||
album: Some("Savanni's Demo".to_owned()),
|
||||
artist: Some("Savanni".to_owned()),
|
||||
duration: Some(15),
|
||||
filetype: "audio/mpeg".parse::<mime::Mime>().unwrap(),
|
||||
},
|
||||
TrackInfo {
|
||||
id: TrackId::from("/home/savanni/Track 3.mp3".to_owned()),
|
||||
|
@ -154,6 +221,8 @@ pub mod factories {
|
|||
name: Some("Track 3".to_owned()),
|
||||
album: Some("Savanni's Demo".to_owned()),
|
||||
artist: Some("Savanni".to_owned()),
|
||||
duration: Some(15),
|
||||
filetype: "audio/mpeg".parse::<mime::Mime>().unwrap(),
|
||||
},
|
||||
TrackInfo {
|
||||
id: TrackId::from("/home/savanni/Track 4.mp3".to_owned()),
|
||||
|
@ -161,6 +230,8 @@ pub mod factories {
|
|||
name: Some("Track 4".to_owned()),
|
||||
album: Some("Savanni's Demo".to_owned()),
|
||||
artist: Some("Savanni".to_owned()),
|
||||
duration: Some(15),
|
||||
filetype: "audio/mpeg".parse::<mime::Mime>().unwrap(),
|
||||
},
|
||||
TrackInfo {
|
||||
id: TrackId::from("/home/savanni/Track 5.mp3".to_owned()),
|
||||
|
@ -168,6 +239,8 @@ pub mod factories {
|
|||
name: Some("Track 5".to_owned()),
|
||||
album: Some("Savanni's Demo".to_owned()),
|
||||
artist: Some("Savanni".to_owned()),
|
||||
duration: Some(15),
|
||||
filetype: "audio/mpeg".parse::<mime::Mime>().unwrap(),
|
||||
},
|
||||
],
|
||||
}
|
Loading…
Reference in New Issue