노래 리스트를 가수-앨범-노래 순으로 정렬 안하면 안되는 병이 있어서
그동안 메론만 썼는데 (구형 멜론 프로그램은 가능합니다.)
멜론에 한 맺힌 게 많아서 알아보다
애플은 직접 어플 뒤져서 찾았고,
유튜브 뮤직은 깃헙에 있는 거 실행이 안돼서 ai한테 도움 받아 수정한 방법입니다.
(https://sagwatteok.tistory.com/m/41)
생각보다 다중 정렬 방법은 찾기 힘들더라구요.
아카이브 겸 필요하실 분 있을까봐 올립니다.
■ 애플 뮤직 (어플)
1. 오른쪽 위 정렬 버튼 클릭
2. 다음으로 보기 – 노래 클릭
3. 노래 – 앨범 – 연도 – 아티스트 순으로 정렬
4. 오른쪽 설정 버튼(…) 클릭
5. 재생순서대로 복사 ★ 필수
6. 플레이리스트 복사 ★ 어쩌면 불필요할 수도 있지만 확실히 하기 위한 방법
■ 유튜브 뮤직 (웹)
1. 사전 설정
- 크롬 확장프로그램 Tampermonkey 설치
- 확장앱 관리 – 권한 – 사용자 스크립트 허용 & 사이트 엑세스 (https://music.youtube.com) 입력
- 권한 문제로 보안 위험 있음 (사용 시에만 Tampermonkey 활성화 조치 / 개인 선택)
2. Tampermonkey 설정
- Tampermonkey 클릭
- 대시보드 클릭
- 상부 메뉴 + 선택
- 아래 스크립트 입력 및 저장
※ 여러 이유로 안되시면 ai의 도움을 받아보세요;;
3. 스크립트
// ==UserScript==
// @name YTMusic Safe Playlist Sort (Strict No Recs)
// @namespace http://tampermonkey.net/
// @version 3.1
// @include https://music.youtube.com/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
let lastUrl = location.href;
// ======================
// 재생목록 영역만 선택 (핵심)
// ======================
function getPlaylistContainer() {
return document.querySelector('ytmusic-playlist-shelf-renderer');
}
function getItems() {
const container = getPlaylistContainer();
if (!container) return [];
const mainListContents = container.querySelector('#contents');
if (!mainListContents) return [];
const items = Array.from(
mainListContents.querySelectorAll(':scope > ytmusic-responsive-list-item-renderer')
);
return items.filter(item => item.querySelector('yt-formatted-string.title'));
}
// ======================
// 텍스트 추출
// ======================
function getTitle(item) {
return item.querySelector('yt-formatted-string.title')?.textContent.trim() ?? '';
}
function getSecondaryTexts(item) {
return Array.from(
item.querySelectorAll('.secondary-flex-columns yt-formatted-string')
)
.map(el => el.textContent.trim())
.filter(Boolean);
}
function getArtist(item) {
return getSecondaryTexts(item)[0] ?? '';
}
function getAlbum(item) {
return getSecondaryTexts(item)[1] ?? '';
}
// ======================
// 정렬 기준
// ======================
function getType(str) {
if (!str) return 3;
const ch = str.trim()[0];
if (/[A-Za-z]/.test(ch)) return 0; // 영어
if (/[가-힣]/.test(ch)) return 1; // 한글
if (/[0-9]/.test(ch)) return 2; // 숫자
return 3; // 기타
}
function compareSmart(a, b) {
const ta = getType(a);
const tb = getType(b);
if (ta !== tb) return ta - tb;
return String(a).localeCompare(String(b), 'ko', {
numeric: true,
sensitivity: 'base'
});
}
// ======================
// 정렬 실행
// ======================
function sortPlaylist() {
const items = getItems();
const container = getPlaylistContainer();
const mainListContents = container ? container.querySelector('#contents') : null;
if (!mainListContents || items.length === 0) {
alert('정렬할 재생목록 곡을 찾지 못했습니다. (추천 곡은 무시됨)');
return;
}
const beforeCount = items.length;
const sorted = [...items].sort((a, b) => {
let cmp = compareSmart(getArtist(a), getArtist(b));
if (cmp !== 0) return cmp;
cmp = compareSmart(getAlbum(a), getAlbum(b));
if (cmp !== 0) return cmp;
return compareSmart(getTitle(a), getTitle(b));
});
const fragment = document.createDocumentFragment();
sorted.forEach(item => fragment.appendChild(item));
mainListContents.appendChild(fragment);
const afterCount = getItems().length;
if (beforeCount !== afterCount) {
alert(
`정렬 완료 (주의)\n` +
`정렬 전: ${beforeCount}\n` +
`정렬 후: ${afterCount}`
);
} else {
alert(`내 곡 정렬 완료 (${afterCount}곡) - 추천 곡 제외됨`);
}
}
// ======================
// UI
// ======================
function injectUI() {
if (!location.href.includes('list=')) return;
if (document.getElementById('ytmusic-sort-ui')) return;
const bar = document.createElement('div');
bar.id = 'ytmusic-sort-ui';
bar.style.cssText = `
position:fixed;
top:90px;
right:20px;
z-index:2147483647;
background:#222;
color:#fff;
padding:10px;
border-radius:10px;
display:flex;
flex-direction:column;
gap:6px;
box-shadow:0 2px 12px rgba(0,0,0,0.55);
font-family:Arial,sans-serif;
`;
const sortBtn = document.createElement('button');
sortBtn.textContent = '정렬';
sortBtn.style.cssText = `
padding:7px 10px;
cursor:pointer;
border-radius:4px;
border:1px solid #555;
background:#333;
color:#fff;
font-weight:bold;
`;
sortBtn.onclick = sortPlaylist;
const closeBtn = document.createElement('button');
closeBtn.textContent = '닫기';
closeBtn.style.cssText = sortBtn.style.cssText;
closeBtn.style.fontWeight = 'normal';
closeBtn.onclick = () => bar.remove();
bar.appendChild(sortBtn);
bar.appendChild(closeBtn);
document.body.appendChild(bar);
}
function boot() {
injectUI();
}
window.addEventListener('load', () => {
setTimeout(boot, 1000);
setTimeout(boot, 3000);
});
setTimeout(boot, 1000);
setTimeout(boot, 3000);
setInterval(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
setTimeout(boot, 1000);
setTimeout(boot, 3000);
}
boot();
}, 2000);
})();