feature:新增整理文件夹

This commit is contained in:
charlesxie
2023-08-04 16:05:47 +08:00
parent 8d5f1214e0
commit 0d19b65e46
13 changed files with 198 additions and 19 deletions

View File

@@ -50,3 +50,11 @@ class FetchLlyricSerializer(serializers.Serializer):
class TranslationLycSerializer(serializers.Serializer):
lyc = serializers.CharField(required=True)
class TidyFolderSerializer(serializers.Serializer):
root_path = serializers.CharField(required=True)
first_dir = serializers.CharField(required=True)
second_dir = serializers.CharField(required=False, allow_null=True, allow_blank=True)
file_full_path = serializers.JSONField(required=True)
select_data = serializers.JSONField(required=True)

View File

@@ -14,12 +14,16 @@ from PIL import Image
class MusicInfo:
def __init__(self, folder: Folder):
self.file = music_tag.load_file(folder.path)
self.path = folder.path
self.folder_name = folder.name
self.file_type = folder.file_type
self.parent_id = folder.parent_id
def __init__(self, folder):
if isinstance(folder, Folder):
self.file = music_tag.load_file(folder.path)
self.path = folder.path
self.folder_name = folder.name
self.file_type = folder.file_type
self.parent_id = folder.parent_id
else:
self.file = music_tag.load_file(folder)
self.path = folder
@property
def album_name(self):
@@ -29,10 +33,23 @@ class MusicInfo:
album_name = "未知专辑"
return self.file["album"].value
@property
def album(self):
album_name = self.file["album"].value
album_name = album_name.replace(" ", "")
if not album_name:
album_name = "未知专辑"
return album_name
return self.file["album"].value
@property
def artist_name(self):
return self.file["artist"].value
@property
def artist(self):
return self.file["artist"].value
@property
def year(self):
try:

View File

@@ -1,8 +1,11 @@
import datetime
import os
import shutil
import time
import uuid
from collections import defaultdict
import music_tag
from django.conf import settings
from django.db import transaction
@@ -10,7 +13,7 @@ from applications.music.models import Folder, Track, Album, Genre, Artist, Attac
from applications.subsonic.constants import AUDIO_EXTENSIONS_AND_MIMETYPE, COVER_TYPE
from applications.task.models import TaskRecord, Task
from applications.task.services.music_resource import MusicResource
from applications.task.services.scan_utils import ScanMusic
from applications.task.services.scan_utils import ScanMusic, MusicInfo
from applications.task.utils import folder_update_time, exists_dir, match_song
from django_vue_cli.celery_app import app
@@ -245,7 +248,7 @@ def batch_auto_tag_task(batch, source_list, select_mode):
folder_list = TaskRecord.objects.filter(batch=batch, icon="icon-folder").all()
for folder in folder_list:
data = os.scandir(folder.full_path)
allow_type = ["flac", "mp3", "ape", "wav", "aiff", "wv", "tta", "m4a", "ogg", "mpc",
allow_type = ["flac", "mp3", "ape", "wav", "aiff", "wv", "tta", "m4a", "ogg", "mpc",
"opus", "wma", "dsf", "dff"]
bulk_set = []
for entry in data:
@@ -287,3 +290,39 @@ def batch_auto_tag_task(batch, source_list, select_mode):
"parent_path": parent_path,
"filename": os.path.basename(task.full_path)
})
def tidy_folder_task(music_path_list, tidy_config):
root_path = tidy_config.get("root_path")
first_dir = tidy_config.get("first_dir")
second_dir = tidy_config.get("second_dir")
if second_dir:
tidy_map = defaultdict(lambda: defaultdict(list))
for music_path in music_path_list:
file = MusicInfo(music_path)
first_value = getattr(file, first_dir, "未知")
second_value = getattr(file, second_dir, "未知")
tidy_map[first_value][second_value].append(music_path)
for first_value, second_map in tidy_map.items():
first_path = os.path.join(root_path, first_value)
if not os.path.exists(first_path):
os.makedirs(first_path)
for second_value, music_path_list in second_map.items():
second_path = os.path.join(first_path, second_value)
if not os.path.exists(second_path):
os.makedirs(second_path)
for music_path in music_path_list:
shutil.move(music_path, second_path)
else:
tidy_map = defaultdict(list)
for music_path in music_path_list:
file = MusicInfo(music_path)
first_value = getattr(file, first_dir, "未知")
tidy_map[first_value].append(music_path)
for first_value, music_path_list in tidy_map.items():
first_path = os.path.join(root_path, first_value)
if not os.path.exists(first_path):
os.makedirs(first_path)
for music_path in music_path_list:
shutil.move(music_path, first_path)

View File

@@ -10,10 +10,11 @@ from rest_framework.decorators import action
from applications.task.models import TaskRecord, Task
from applications.task.serialziers import FileListSerializer, Id3Serializer, UpdateId3Serializer, \
FetchId3ByTitleSerializer, FetchLlyricSerializer, BatchUpdateId3Serializer, TranslationLycSerializer
FetchId3ByTitleSerializer, FetchLlyricSerializer, BatchUpdateId3Serializer, TranslationLycSerializer, \
TidyFolderSerializer
from applications.task.services.music_resource import MusicResource
from applications.task.services.update_ids import update_music_info
from applications.task.tasks import full_scan_folder, scan, clear_music, batch_auto_tag_task
from applications.task.tasks import full_scan_folder, scan, clear_music, batch_auto_tag_task, tidy_folder_task
from applications.utils.translation import translation_lyc_text
from component.drf.viewsets import GenericViewSet
from django_vue_cli.celery_app import app as celery_app
@@ -36,6 +37,8 @@ class TaskViewSets(GenericViewSet):
return BatchUpdateId3Serializer
elif self.action == "translation_lyc":
return TranslationLycSerializer
elif self.action == "tidy_folder":
return TidyFolderSerializer
return FileListSerializer
@action(methods=['POST'], detail=False)
@@ -259,6 +262,32 @@ class TaskViewSets(GenericViewSet):
new_lyc.append(f"{raw_src}\n{result}\n")
return self.success_response(data="\n".join(new_lyc))
@action(methods=['POST'], detail=False)
def tidy_folder(self, request, *args, **kwargs):
validate_data = self.is_validated_data(request.data)
root_path = validate_data["root_path"]
first_dir = validate_data["first_dir"]
full_path = validate_data["file_full_path"]
select_data = validate_data["select_data"]
second_dir = validate_data.get("second_dir", "")
music_id3_info = []
for data in select_data:
if data.get('icon') == 'icon-folder':
file_full_path = f"{full_path}/{data.get('name')}"
data = os.scandir(file_full_path)
allow_type = ["flac", "mp3", "ape", "wav", "aiff", "wv", "tta", "m4a", "ogg", "mpc",
"opus", "wma", "dsf", "dff"]
for index, entry in enumerate(data, 1):
each = entry.name
file_type = each.split(".")[-1]
if file_type not in allow_type:
continue
music_id3_info.append(f"{file_full_path}/{each}")
else:
music_id3_info.append(f"{full_path}/{data.get('name')}")
tidy_folder_task(music_id3_info, {"root_path": root_path, "first_dir": first_dir, "second_dir": second_dir})
return self.success_response()
@action(methods=["get"], detail=False)
def clear_celery(self, request, *args, **kwargs):
active_tasks = celery_app.control.inspect().active()

View File

@@ -156,12 +156,12 @@ REST_FRAMEWORK = {
JWT_AUTH = {
# 过期时间生成的took七天之后不能使用
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
# 刷新时间 之后的token时间值
# 'JWT_ALLOW_REFRESH': True,
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=1),
'JWT_ALLOW_REFRESH': True,
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
# 请求头携带的参数
'JWT_AUTH_HEADER_PREFIX': 'JWT',
'JWT_AUTH_HEADER_PREFIX': 'JWT'
}
BASE_URL = "https://music.163.com/"
REVERSE_PROXY_TYPE = "nginx"

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,3 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>音乐标签Web版Music Tag Web</title><link rel="shortcut icon" href=/static/dist/img/music-tag.png type=image/x-icon><link href=./static/dist/css/app.css rel=stylesheet></head><body><script>window.siteUrl = "/"
window.APP_CODE = 'dj-flow';
window.CSRF_COOKIE_NAME = 'django_vue_cli_csrftoken'</script><div id=app></div><script type=text/javascript src=./static/dist/js/manifest.9ba6c0d4f4490e9a4f28.js></script><script type=text/javascript src=./static/dist/js/vendor.051dd49be048f27f51f9.js></script><script type=text/javascript src=./static/dist/js/app.fec4fc7ab40038232872.js></script></body></html>
window.CSRF_COOKIE_NAME = 'django_vue_cli_csrftoken'</script><div id=app></div><script type=text/javascript src=./static/dist/js/manifest.9ba6c0d4f4490e9a4f28.js></script><script type=text/javascript src=./static/dist/js/vendor.051dd49be048f27f51f9.js></script><script type=text/javascript src=./static/dist/js/app.f642b95eddd98320a0c9.js></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -25,6 +25,9 @@ export default {
batchAutoUpdateId3: function(params) {
return POST(reUrl + '/api/batch_auto_update_id3/', params)
},
tidyFolder: function(params) {
return POST(reUrl + '/api/tidy_folder/', params)
},
fetchId3Title: function(params) {
return POST(reUrl + '/api/fetch_id3_by_title/', params)
},

View File

@@ -56,7 +56,7 @@ Vue.prototype.setCookie = function(name, value, day) {
const curTamp = curDate.getTime()
const curWeeHours = new Date(curDate.toLocaleDateString()).getTime() - 1
const passedTamp = curTamp - curWeeHours
const leftTamp = 24 * 60 * 60 * 1000 - passedTamp
const leftTamp = 7 * 24 * 60 * 60 * 1000 - passedTamp
const leftTime = new Date()
leftTime.setTime(leftTamp + curTamp)
document.cookie = name + '=' + escape(value) + ';expires=' + leftTime.toGMTString()

View File

@@ -176,6 +176,13 @@
自动批量修改
</bk-button>
</div>
<div style="width: 100%;display: flex;margin-top: 10px;">
<bk-button :theme="'success'" :loading="isLoading"
@click="exampleSetting2.primary.visible = true" class="mr10"
style="width: 50%;">
整理文件夹
</bk-button>
</div>
<div style="display: flex;margin-bottom: 10px;align-items: center;margin-top: 10px;">
<div class="label1" v-bk-tooltips="'变量名:${title}'">标题</div>
<div style="width: 70%;">
@@ -386,6 +393,38 @@
</bk-option>
</bk-select>
</bk-dialog>
<bk-dialog v-model="exampleSetting2.primary.visible"
theme="primary"
:mask-close="false"
@confirm="handleTidy"
:header-position="exampleSetting2.primary.headerPosition"
title="自动批量修改">
<p>整理文件夹按一级目录二级目录选定的信息分类</p>
<div>整理后的根目录</div>
<div class="input-demo">
<bk-input v-model="tidyFormData.root_path">
</bk-input>
</div>
<div>一级目录</div>
<bk-select style="width: 250px;"
:clearable="false"
v-model="tidyFormData.first_dir">
<bk-option v-for="option in tidyList"
:key="option.id"
:id="option.id"
:name="option.name">
</bk-option>
</bk-select>
<div>二级目录</div>
<bk-select style="width: 250px;"
v-model="tidyFormData.second_dir">
<bk-option v-for="option in tidyList"
:key="option.id"
:id="option.id"
:name="option.name">
</bk-option>
</bk-select>
</bk-dialog>
</div>
</template>
<script>
@@ -407,6 +446,13 @@
{id: 'kugou', name: '酷狗音乐'},
{id: 'kuwo', name: '酷我音乐'}
],
tidyList: [
{id: 'title', name: '标题'},
{id: 'artist', name: '艺术家'},
{id: 'album', name: '专辑'},
{id: 'genre', name: '风格'},
{id: 'comment', name: '描述'}
],
baseMusicInfo: {
'genre': '流行',
'is_save_lyrics_file': false
@@ -444,11 +490,22 @@
checkedData: [],
selectAutoMode: 'hard',
sourceList: [],
tidyFormData: {
root_path: '/app/media/',
first_dir: 'artist',
second_dir: ''
},
exampleSetting1: {
primary: {
visible: false,
headerPosition: 'left'
}
},
exampleSetting2: {
primary: {
visible: false,
headerPosition: 'left'
}
}
}
},
@@ -704,6 +761,32 @@
}
}
})
},
handleTidy() {
this.$bkInfo({
title: '确认要整理文件夹?',
confirmLoading: true,
confirmFn: () => {
try {
this.isLoading = true
this.tidyFormData['file_full_path'] = this.filePath
this.tidyFormData['select_data'] = this.checkedData
this.$api.Task.tidyFolder(this.tidyFormData).then((res) => {
this.isLoading = false
console.log(res)
if (res.result) {
this.$cwMessage('创建成功', 'success')
} else {
this.$cwMessage('创建失败', 'error')
}
})
return true
} catch (e) {
console.warn(e)
return false
}
}
})
}
}
}

View File

@@ -38,7 +38,7 @@
methods: {
submitLogin() {
this.$api.Task.login(this.formData).then((res) => {
this.setCookie('AUTHORIZATION', 'jwt ' + res.token)
this.setCookie('AUTHORIZATION', 'jwt ' + res.token, 7)
this.$router.push({name: 'home'})
})
}