Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions electron/main/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const initDatabase = (): void => {
id TEXT PRIMARY KEY,
path TEXT NOT NULL UNIQUE,
title TEXT NOT NULL,
track INTEGER,
artists TEXT NOT NULL DEFAULT '[]',
album TEXT,
duration INTEGER NOT NULL,
Expand Down
10 changes: 9 additions & 1 deletion electron/main/database/migration.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type Database from "better-sqlite3";

/** 当前 schema 版本 */
const SCHEMA_VERSION = 2;
const SCHEMA_VERSION = 3;

type TableInfoRow = { name: string };

Expand All @@ -27,6 +27,14 @@ export const migrate = (d: Database.Database): void => {
v = 2;
}

// v2 → v3: 添加 track 列
if (v < 3) {
if (!hasColumn(d, "tracks", "track")) {
d.exec("ALTER TABLE tracks ADD COLUMN track INTEGER");
}
v = 3;
}

// 版本无关部分
// 补 lyric_match_cache.extra 列
if (!hasColumn(d, "lyric_match_cache", "extra")) {
Expand Down
8 changes: 6 additions & 2 deletions electron/main/database/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface TrackRow {
id: string;
path: string;
title: string;
track?: number;
artists: string;
album: string | null;
duration: number;
Expand Down Expand Up @@ -41,6 +42,7 @@ const rowToTrack = (row: TrackRow): Track => {
source: "local",
path: row.path,
title: row.title,
track: row.track ?? undefined,
artists: JSON.parse(row.artists) as Artist[],
album: row.album ? (JSON.parse(row.album) as Album) : undefined,
duration: row.duration,
Expand Down Expand Up @@ -101,6 +103,7 @@ export interface UpsertTrack {
id: string;
path: string;
title: string;
track?: number;
artists: Artist[];
album?: Album;
duration: number;
Expand All @@ -121,9 +124,9 @@ export const upsertTracks = (tracks: UpsertTrack[]): void => {
const d = getDb();
const stmt = d.prepare(`
INSERT OR REPLACE INTO tracks
(id, path, title, artists, album, duration, cover, codec, sample_rate, bit_rate, channels, bits_per_sample, file_size, file_mtime, file_ctime, scanned_at)
(id, path, title, track, artists, album, duration, cover, codec, sample_rate, bit_rate, channels, bits_per_sample, file_size, file_mtime, file_ctime, scanned_at)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
const now = Date.now();
const tx = d.transaction(() => {
Expand All @@ -132,6 +135,7 @@ export const upsertTracks = (tracks: UpsertTrack[]): void => {
t.id,
t.path,
t.title,
t.track ?? null,
JSON.stringify(t.artists),
t.album ? JSON.stringify(t.album) : null,
t.duration,
Expand Down
1 change: 1 addition & 0 deletions electron/main/services/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const scannedToUpsert = (track: JsScannedTrack): UpsertTrack => {
id,
path: track.path,
title: track.title || track.path.split(/[/\\]/).pop() || track.path,
track: track.track,
artists: parseArtists(track.artist ?? ""),
album: parseAlbum(track.album ?? ""),
duration: toMs(track.duration),
Expand Down
2 changes: 2 additions & 0 deletions native/audio-engine/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ export interface JsScannedTrack {
title?: string
artist?: string
album?: string
/** 音轨编号 */
track?: number
/** 时长(秒) */
duration: number
codec: string
Expand Down
3 changes: 3 additions & 0 deletions native/audio-engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,8 @@ pub struct JsScannedTrack {
pub title: Option<String>,
pub artist: Option<String>,
pub album: Option<String>,
/// 音轨编号
pub track: Option<u16>,
/// 时长(秒)
pub duration: f64,
pub codec: String,
Expand All @@ -678,6 +680,7 @@ impl From<scanner::ScannedTrack> for JsScannedTrack {
title: track.title,
artist: track.artist,
album: track.album,
track: track.track,
duration: track.duration,
codec: track.codec,
sample_rate: track.sample_rate,
Expand Down
3 changes: 3 additions & 0 deletions native/audio-engine/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub struct Tags {
pub title: Option<String>,
pub artist: Option<String>,
pub album: Option<String>,
pub track: Option<u16>,
pub comment: Option<String>,
}

Expand Down Expand Up @@ -60,11 +61,13 @@ pub fn extract_tags(dict: &HashMap<String, String>) -> Tags {
.or_else(|| dict_get(dict, "album_artist"))
.map(ToString::to_string);
let album = dict_get(dict, "album").map(ToString::to_string);
let track = dict_get(dict, "track").and_then(|s| s.parse().ok());
let comment = dict_get(dict, "comment").map(ToString::to_string);
Tags {
title,
artist,
album,
track,
comment,
}
}
Expand Down
2 changes: 2 additions & 0 deletions native/audio-engine/src/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub struct ScannedTrack {
pub title: Option<String>,
pub artist: Option<String>,
pub album: Option<String>,
pub track: Option<u16>,
pub duration: f64,
pub codec: String,
pub sample_rate: u32,
Expand Down Expand Up @@ -106,6 +107,7 @@ pub(crate) fn probe_fast(path: &str, cover_cache_dir: Option<&str>) -> Option<Sc
title: tags.title,
artist: tags.artist,
album: tags.album,
track: tags.track,
duration,
codec,
sample_rate: stream_info.sample_rate,
Expand Down
2 changes: 2 additions & 0 deletions shared/types/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export interface Track {
artists: Artist[];
/** 专辑 */
album?: Album;
/** 曲目编号 */
track?: number;
/** 时长(毫秒) */
duration: number;
/** 封面 */
Expand Down
21 changes: 19 additions & 2 deletions src/components/list/SongList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,17 @@ const goAlbum = (item: Track): void => {
};

/** 排序字段 */
type SortField = "none" | "title" | "artist" | "album" | "duration" | "size" | "mtime" | "ctime";
type SortField =
| "none"
| "title"
| "artist"
| "album"
| "path"
| "duration"
| "size"
| "mtime"
| "ctime"
| "track";
/** 排序方向 */
type SortOrder = "asc" | "desc";

Expand All @@ -136,10 +146,12 @@ const sortFieldLabelKeyMap: Record<SortField, string> = {
title: "songList.sort.byTitle",
artist: "songList.sort.byArtist",
album: "songList.sort.byAlbum",
path: "songList.sort.byPath",
duration: "songList.sort.byDuration",
size: "songList.sort.bySize",
mtime: "songList.sort.byMtime",
ctime: "songList.sort.byCtime",
track: "songList.sort.byTrack",
};

/** 表头显示的当前排序文案 */
Expand Down Expand Up @@ -171,16 +183,19 @@ const sortedItems = computed(() => {

const toArtistText = (track: Track): string => track.artists.map((a) => a.name).join(" / ");
const toAlbumText = (track: Track): string => track.album?.name ?? "";
const toPathText = (track: Track): string => track.path ?? "";

const compare = (a: Track, b: Track): number => {
let value = 0;
if (field === "title") value = textCollator.compare(a.title, b.title);
else if (field === "artist") value = textCollator.compare(toArtistText(a), toArtistText(b));
else if (field === "album") value = textCollator.compare(toAlbumText(a), toAlbumText(b));
else if (field === "path") value = textCollator.compare(toPathText(a), toPathText(b));
else if (field === "duration") value = a.duration - b.duration;
else if (field === "size") value = (a.fileSize ?? 0) - (b.fileSize ?? 0);
else if (field === "mtime") value = (a.mtime ?? 0) - (b.mtime ?? 0);
else value = (a.ctime ?? 0) - (b.ctime ?? 0);
else if (field === "ctime") value = (a.ctime ?? 0) - (b.ctime ?? 0);
else /*(field === "track")*/ value = (a.track ?? 0) - (b.track ?? 0);
if (value !== 0) return value * direction;
const fallback = textCollator.compare(a.title, b.title);
if (fallback !== 0) return fallback;
Expand Down Expand Up @@ -455,10 +470,12 @@ defineExpose({
<SRadio value="title" :label="t('songList.sort.byTitle')" />
<SRadio value="artist" :label="t('songList.sort.byArtist')" />
<SRadio value="album" :label="t('songList.sort.byAlbum')" />
<SRadio value="path" :label="t('songList.sort.byPath')" />
<SRadio value="duration" :label="t('songList.sort.byDuration')" />
<SRadio value="size" :label="t('songList.sort.bySize')" />
<SRadio value="mtime" :label="t('songList.sort.byMtime')" />
<SRadio value="ctime" :label="t('songList.sort.byCtime')" />
<SRadio value="track" :label="t('songList.sort.byTrack')" />
</SRadioGroup>

<div class="h-px bg-outline-variant/25" />
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -446,13 +446,15 @@
"desc": "Descending",
"byTitle": "Sort by Title",
"byArtist": "Sort by Artist",
"byPath": "Sort by Local Path",
"byAlbum": "Sort by Album",
"byDuration": "Sort by Duration",
"bySize": "Sort by Size",
"byMtime": "Sort by Modified Time",
"byCtime": "Sort by Created Time",
"byName": "Sort by Name",
"byTrackCount": "Sort by Song Count"
"byTrackCount": "Sort by Song Count",
"byTrack": "Sort by Track Number"
},
"context": {
"play": "Play",
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -435,12 +435,14 @@
"byTitle": "按标题",
"byArtist": "按歌手",
"byAlbum": "按专辑",
"byPath": "按文件路径",
"byDuration": "按时长",
"bySize": "按大小",
"byMtime": "按修改时间",
"byCtime": "按创建时间",
"byName": "按名称",
"byTrackCount": "按歌曲数量"
"byTrackCount": "按歌曲数量",
"byTrack": "按曲目编号"
},
"context": {
"play": "播放",
Expand Down