mirror of
https://github.com/xhongc/music-tag-web.git
synced 2026-02-02 17:59:07 +08:00
feature:支持繁体匹配,新增消息中心展示自动刮削不匹配的数据,
This commit is contained in:
5
applications/task/filters.py
Normal file
5
applications/task/filters.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import django_filters
|
||||
|
||||
|
||||
class TaskFilters(django_filters.FilterSet):
|
||||
state = django_filters.CharFilter(lookup_expr="iexact")
|
||||
28
applications/task/migrations/0006_auto_20230830_1458.py
Normal file
28
applications/task/migrations/0006_auto_20230830_1458.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 2.2.6 on 2023-08-30 14:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('task', '0005_auto_20230711_1403'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='artist_name',
|
||||
field=models.CharField(default='', max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='song_name',
|
||||
field=models.CharField(default='', max_length=255),
|
||||
),
|
||||
]
|
||||
@@ -2,10 +2,14 @@ from django.db import models
|
||||
|
||||
|
||||
class Task(models.Model):
|
||||
song_name = models.CharField(max_length=255, default="")
|
||||
artist_name = models.CharField(max_length=255, default="")
|
||||
|
||||
full_path = models.CharField(max_length=255)
|
||||
state = models.CharField(max_length=255, default="wait")
|
||||
parent_path = models.CharField(max_length=255, default="")
|
||||
filename = models.CharField(max_length=255, default="")
|
||||
created_at = models.DateTimeField(null=True, auto_now_add=True)
|
||||
|
||||
|
||||
class TaskRecord(models.Model):
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import os.path
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from applications.task.models import TaskRecord, Task
|
||||
|
||||
|
||||
class FileListSerializer(serializers.Serializer):
|
||||
file_path = serializers.CharField(required=True)
|
||||
@@ -61,3 +65,23 @@ class TidyFolderSerializer(serializers.Serializer):
|
||||
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)
|
||||
|
||||
|
||||
class TaskSerializer(serializers.ModelSerializer):
|
||||
message = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Task
|
||||
fields = "__all__"
|
||||
|
||||
def get_message(self, obj):
|
||||
return f"【{obj.song_name}】 未找到标签或修改失败!"
|
||||
|
||||
def to_representation(self, instance):
|
||||
ret = super().to_representation(instance)
|
||||
if os.path.exists(ret["full_path"]):
|
||||
ret["is_exists"] = True
|
||||
else:
|
||||
Task.objects.filter(id=ret["id"]).delete()
|
||||
ret["is_exists"] = False
|
||||
return ret
|
||||
|
||||
@@ -278,7 +278,9 @@ def batch_auto_tag_task(batch, source_list, select_mode):
|
||||
Task.objects.update_or_create(full_path=task.full_path, defaults={
|
||||
"state": task.state,
|
||||
"parent_path": parent_path,
|
||||
"filename": os.path.basename(task.full_path)
|
||||
"filename": os.path.basename(task.full_path),
|
||||
"song_name": task.song_name,
|
||||
"artist_name": task.artist_name,
|
||||
})
|
||||
break
|
||||
if not is_match:
|
||||
@@ -288,7 +290,9 @@ def batch_auto_tag_task(batch, source_list, select_mode):
|
||||
Task.objects.update_or_create(full_path=task.full_path, defaults={
|
||||
"state": task.state,
|
||||
"parent_path": parent_path,
|
||||
"filename": os.path.basename(task.full_path)
|
||||
"filename": os.path.basename(task.full_path),
|
||||
"song_name": task.song_name,
|
||||
"artist_name": task.artist_name,
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -4,4 +4,5 @@ from . import views
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r"", views.TaskViewSets, base_name='task')
|
||||
router.register(r"record", views.TaskModelViewSets, base_name='record')
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import time
|
||||
import music_tag
|
||||
|
||||
from applications.task.services.update_ids import save_music
|
||||
from component.zhconv.zhconv import convert, issimp
|
||||
|
||||
|
||||
def timestamp_to_dt(timestamp, format_type="%Y-%m-%d %H:%M:%S"):
|
||||
@@ -34,6 +35,10 @@ def match_score(my_value, u_value):
|
||||
try:
|
||||
my_value = my_value.lower().replace(" ", "")
|
||||
u_value = u_value.lower().replace(" ", "")
|
||||
if not issimp(my_value):
|
||||
my_value = convert(my_value, 'zh-cn')
|
||||
if not issimp(u_value):
|
||||
u_value = convert(u_value, 'zh-cn')
|
||||
if not my_value or not u_value:
|
||||
return 0
|
||||
if my_value == u_value:
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import base64
|
||||
import copy
|
||||
import locale
|
||||
import copy
|
||||
import os
|
||||
import time
|
||||
|
||||
import music_tag
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.gzip import gzip_page
|
||||
from rest_framework import mixins
|
||||
from rest_framework.decorators import action
|
||||
|
||||
from applications.task.constants import ALLOW_TYPE
|
||||
from applications.task.filters import TaskFilters
|
||||
from applications.task.models import TaskRecord, Task
|
||||
from applications.task.serialziers import FileListSerializer, Id3Serializer, UpdateId3Serializer, \
|
||||
FetchId3ByTitleSerializer, FetchLlyricSerializer, BatchUpdateId3Serializer, TranslationLycSerializer, \
|
||||
TidyFolderSerializer
|
||||
TidyFolderSerializer, TaskSerializer
|
||||
from applications.task.services.music_ids import MusicIDS
|
||||
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, tidy_folder_task
|
||||
from applications.task.utils import match_score
|
||||
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
|
||||
@@ -324,3 +323,10 @@ class TaskViewSets(GenericViewSet):
|
||||
def full_scan_folder(self, request, *args, **kwargs):
|
||||
full_scan_folder.delay()
|
||||
return self.success_response()
|
||||
|
||||
|
||||
class TaskModelViewSets(mixins.ListModelMixin,
|
||||
GenericViewSet):
|
||||
queryset = Task.objects.order_by("-id")
|
||||
serializer_class = TaskSerializer
|
||||
filterset_class = TaskFilters
|
||||
|
||||
26406
component/zhconv/zhcdict.json
Normal file
26406
component/zhconv/zhcdict.json
Normal file
File diff suppressed because one or more lines are too long
455
component/zhconv/zhconv.py
Normal file
455
component/zhconv/zhconv.py
Normal file
@@ -0,0 +1,455 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Only Python3 can pass the doctest here due to unicode problems.
|
||||
__version__ = '1.4.2'
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import json
|
||||
|
||||
try:
|
||||
from pkg_resources import resource_stream
|
||||
get_module_res = lambda *res: resource_stream(__name__, os.path.join(*res))
|
||||
except ImportError:
|
||||
get_module_res = lambda *res: open(os.path.normpath(
|
||||
os.path.join(os.getcwd(), os.path.dirname(__file__), *res)), 'rb')
|
||||
|
||||
# Locale fallback order lookup dictionary
|
||||
Locales = {
|
||||
'zh-cn': ('zh-cn', 'zh-hans', 'zh-sg', 'zh'),
|
||||
'zh-hk': ('zh-hk', 'zh-hant', 'zh-tw', 'zh'),
|
||||
'zh-tw': ('zh-tw', 'zh-hant', 'zh-hk', 'zh'),
|
||||
'zh-sg': ('zh-sg', 'zh-hans', 'zh-cn', 'zh'),
|
||||
'zh-my': ('zh-my', 'zh-sg', 'zh-hans', 'zh-cn', 'zh'),
|
||||
'zh-mo': ('zh-mo', 'zh-hk', 'zh-hant', 'zh-tw', 'zh'),
|
||||
'zh-hant': ('zh-hant', 'zh-tw', 'zh-hk', 'zh'),
|
||||
'zh-hans': ('zh-hans', 'zh-cn', 'zh-sg', 'zh'),
|
||||
'zh': ('zh',) # special value for no conversion
|
||||
}
|
||||
|
||||
_DEFAULT_DICT = "zhcdict.json"
|
||||
DICTIONARY = _DEFAULT_DICT
|
||||
|
||||
zhcdicts = None
|
||||
dict_zhcn = None
|
||||
dict_zhsg = None
|
||||
dict_zhtw = None
|
||||
dict_zhhk = None
|
||||
pfsdict = {}
|
||||
|
||||
RE_langconv = re.compile(r'(-\{|\}-)')
|
||||
RE_splitflag = re.compile(r'\s*\|\s*')
|
||||
RE_splitmap = re.compile(r'\s*;\s*')
|
||||
RE_splituni = re.compile(r'\s*=>\s*')
|
||||
RE_splitpair = re.compile(r'\s*:\s*')
|
||||
|
||||
def loaddict(filename=DICTIONARY):
|
||||
"""
|
||||
Load the dictionary from a specific JSON file.
|
||||
"""
|
||||
global zhcdicts
|
||||
if zhcdicts:
|
||||
return
|
||||
if filename == _DEFAULT_DICT:
|
||||
zhcdicts = json.loads(get_module_res(filename).read().decode('utf-8'))
|
||||
else:
|
||||
with open(filename, 'rb') as f:
|
||||
zhcdicts = json.loads(f.read().decode('utf-8'))
|
||||
zhcdicts['SIMPONLY'] = frozenset(zhcdicts['SIMPONLY'])
|
||||
zhcdicts['TRADONLY'] = frozenset(zhcdicts['TRADONLY'])
|
||||
|
||||
def getdict(locale):
|
||||
"""
|
||||
Generate or get convertion dict cache for certain locale.
|
||||
Dictionaries are loaded on demand.
|
||||
"""
|
||||
global zhcdicts, dict_zhcn, dict_zhsg, dict_zhtw, dict_zhhk, pfsdict
|
||||
if zhcdicts is None:
|
||||
loaddict(DICTIONARY)
|
||||
if locale == 'zh-cn':
|
||||
if dict_zhcn:
|
||||
got = dict_zhcn
|
||||
else:
|
||||
dict_zhcn = zhcdicts['zh2Hans'].copy()
|
||||
dict_zhcn.update(zhcdicts['zh2CN'])
|
||||
got = dict_zhcn
|
||||
elif locale == 'zh-tw':
|
||||
if dict_zhtw:
|
||||
got = dict_zhtw
|
||||
else:
|
||||
dict_zhtw = zhcdicts['zh2Hant'].copy()
|
||||
dict_zhtw.update(zhcdicts['zh2TW'])
|
||||
got = dict_zhtw
|
||||
elif locale == 'zh-hk' or locale == 'zh-mo':
|
||||
if dict_zhhk:
|
||||
got = dict_zhhk
|
||||
else:
|
||||
dict_zhhk = zhcdicts['zh2Hant'].copy()
|
||||
dict_zhhk.update(zhcdicts['zh2HK'])
|
||||
got = dict_zhhk
|
||||
elif locale == 'zh-sg' or locale == 'zh-my':
|
||||
if dict_zhsg:
|
||||
got = dict_zhsg
|
||||
else:
|
||||
dict_zhsg = zhcdicts['zh2Hans'].copy()
|
||||
dict_zhsg.update(zhcdicts['zh2SG'])
|
||||
got = dict_zhsg
|
||||
elif locale == 'zh-hans':
|
||||
got = zhcdicts['zh2Hans']
|
||||
elif locale == 'zh-hant':
|
||||
got = zhcdicts['zh2Hant']
|
||||
else:
|
||||
got = {}
|
||||
if locale not in pfsdict:
|
||||
pfsdict[locale] = getpfset(got)
|
||||
return got
|
||||
|
||||
def getpfset(convdict):
|
||||
pfset = []
|
||||
for word in convdict:
|
||||
for ch in range(len(word)):
|
||||
pfset.append(word[:ch+1])
|
||||
return frozenset(pfset)
|
||||
|
||||
def issimp(s, full=False):
|
||||
"""
|
||||
Detect text is whether Simplified Chinese or Traditional Chinese.
|
||||
Returns True for Simplified; False for Traditional; None for unknown.
|
||||
If full=False, it returns once first simplified- or traditional-only
|
||||
character is encountered, so it's for quick and rough identification;
|
||||
else, it compares the count and returns the most likely one.
|
||||
Use `is` (True/False/None) to check the result.
|
||||
|
||||
`s` must be unicode (Python 2) or str (Python 3), or you'll get None.
|
||||
"""
|
||||
if zhcdicts is None:
|
||||
loaddict(DICTIONARY)
|
||||
simp, trad = 0, 0
|
||||
if full:
|
||||
for ch in s:
|
||||
if ch in zhcdicts['SIMPONLY']:
|
||||
simp += 1
|
||||
elif ch in zhcdicts['TRADONLY']:
|
||||
trad += 1
|
||||
if simp > trad:
|
||||
return True
|
||||
elif simp < trad:
|
||||
return False
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
for ch in s:
|
||||
if ch in zhcdicts['SIMPONLY']:
|
||||
return True
|
||||
elif ch in zhcdicts['TRADONLY']:
|
||||
return False
|
||||
return None
|
||||
|
||||
def fallback(locale, mapping):
|
||||
for l in Locales[locale]:
|
||||
if l in mapping:
|
||||
return mapping[l]
|
||||
return convert(tuple(mapping.values())[0], locale)
|
||||
|
||||
def convtable2dict(convtable, locale, update=None):
|
||||
"""
|
||||
Convert a list of conversion dict to a dict for a certain locale.
|
||||
|
||||
>>> sorted(convtable2dict([{'zh-hk': '列斯', 'zh-hans': '利兹', 'zh': '利兹', 'zh-tw': '里茲'}, {':uni': '巨集', 'zh-cn': '宏'}], 'zh-cn').items())
|
||||
[('列斯', '利兹'), ('利兹', '利兹'), ('巨集', '宏'), ('里茲', '利兹')]
|
||||
"""
|
||||
rdict = update.copy() if update else {}
|
||||
for r in convtable:
|
||||
if ':uni' in r:
|
||||
if locale in r:
|
||||
rdict[r[':uni']] = r[locale]
|
||||
elif locale[:-1] == 'zh-han':
|
||||
if locale in r:
|
||||
for word in r.values():
|
||||
rdict[word] = r[locale]
|
||||
else:
|
||||
v = fallback(locale, r)
|
||||
for word in r.values():
|
||||
rdict[word] = v
|
||||
return rdict
|
||||
|
||||
def tokenize(s, locale, update=None):
|
||||
"""
|
||||
Tokenize `s` according to corresponding locale dictionary.
|
||||
Don't use this for serious text processing.
|
||||
"""
|
||||
zhdict = getdict(locale)
|
||||
pfset = pfsdict[locale]
|
||||
if update:
|
||||
zhdict = zhdict.copy()
|
||||
zhdict.update(update)
|
||||
newset = set()
|
||||
for word in update:
|
||||
for ch in range(len(word)):
|
||||
newset.add(word[:ch+1])
|
||||
pfset = pfset | newset
|
||||
ch = []
|
||||
N = len(s)
|
||||
pos = 0
|
||||
while pos < N:
|
||||
i = pos
|
||||
frag = s[pos]
|
||||
maxword = None
|
||||
maxpos = 0
|
||||
while i < N and frag in pfset:
|
||||
if frag in zhdict:
|
||||
maxword = frag
|
||||
maxpos = i
|
||||
i += 1
|
||||
frag = s[pos:i+1]
|
||||
if maxword is None:
|
||||
maxword = s[pos]
|
||||
pos += 1
|
||||
else:
|
||||
pos = maxpos + 1
|
||||
ch.append(maxword)
|
||||
return ch
|
||||
|
||||
def convert(s, locale, update=None):
|
||||
"""
|
||||
Main convert function.
|
||||
|
||||
:param s: must be `unicode` (Python 2) or `str` (Python 3).
|
||||
:param locale: should be one of ``('zh-hans', 'zh-hant', 'zh-cn', 'zh-sg'
|
||||
'zh-tw', 'zh-hk', 'zh-my', 'zh-mo')``.
|
||||
:param update: a dict which updates the conversion table, eg.
|
||||
``{'from1': 'to1', 'from2': 'to2'}``
|
||||
|
||||
>>> print(convert('我幹什麼不干你事。', 'zh-cn'))
|
||||
我干什么不干你事。
|
||||
>>> print(convert('我幹什麼不干你事。', 'zh-cn', {'不干': '不幹'}))
|
||||
我干什么不幹你事。
|
||||
>>> print(convert('人体内存在很多微生物', 'zh-tw'))
|
||||
人體內存在很多微生物
|
||||
"""
|
||||
if locale == 'zh' or locale not in Locales:
|
||||
# "no conversion"
|
||||
return s
|
||||
zhdict = getdict(locale)
|
||||
pfset = pfsdict[locale]
|
||||
newset = set()
|
||||
if update:
|
||||
newset = set()
|
||||
for word in update:
|
||||
for ch in range(len(word)):
|
||||
newset.add(word[:ch+1])
|
||||
#pfset = pfset | newset
|
||||
ch = []
|
||||
N = len(s)
|
||||
pos = 0
|
||||
while pos < N:
|
||||
i = pos
|
||||
frag = s[pos]
|
||||
maxword = None
|
||||
maxpos = 0
|
||||
while i < N and (frag in pfset or frag in newset):
|
||||
if update and frag in update:
|
||||
maxword = update[frag]
|
||||
maxpos = i
|
||||
elif frag in zhdict:
|
||||
maxword = zhdict[frag]
|
||||
maxpos = i
|
||||
i += 1
|
||||
frag = s[pos:i+1]
|
||||
if maxword is None:
|
||||
maxword = s[pos]
|
||||
pos += 1
|
||||
else:
|
||||
pos = maxpos + 1
|
||||
ch.append(maxword)
|
||||
return ''.join(ch)
|
||||
|
||||
def convert_for_mw(s, locale, update=None):
|
||||
"""
|
||||
Recognizes MediaWiki's human conversion format.
|
||||
Use locale='zh' for no conversion.
|
||||
|
||||
Reference: (all tests passed)
|
||||
https://zh.wikipedia.org/wiki/Help:高级字词转换语法
|
||||
https://www.mediawiki.org/wiki/Writing_systems/Syntax
|
||||
|
||||
>>> print(convert_for_mw('在现代,机械计算-{}-机的应用已经完全被电子计算-{}-机所取代', 'zh-hk'))
|
||||
在現代,機械計算機的應用已經完全被電子計算機所取代
|
||||
>>> print(convert_for_mw('-{zh-hant:資訊工程;zh-hans:计算机工程学;}-是电子工程的一个分支,主要研究计算机软硬件和二者间的彼此联系。', 'zh-tw'))
|
||||
資訊工程是電子工程的一個分支,主要研究計算機軟硬體和二者間的彼此聯繫。
|
||||
>>> print(convert_for_mw('張國榮曾在英國-{zh:利兹;zh-hans:利兹;zh-hk:列斯;zh-tw:里茲}-大学學習。', 'zh-hant'))
|
||||
張國榮曾在英國里茲大學學習。
|
||||
>>> print(convert_for_mw('張國榮曾在英國-{zh:利兹;zh-hans:利兹;zh-hk:列斯;zh-tw:里茲}-大学學習。', 'zh-sg'))
|
||||
张国荣曾在英国利兹大学学习。
|
||||
>>> convert_for_mw('-{zh-hant:;\\nzh-cn:}-', 'zh-tw') == ''
|
||||
True
|
||||
>>> print(convert_for_mw('毫米(毫公分),符號mm,是長度單位和降雨量單位,-{zh-hans:台湾作-{公釐}-或-{公厘}-;zh-hant:港澳和大陸稱為-{毫米}-(台灣亦有使用,但較常使用名稱為毫公分);zh-mo:台灣作-{公釐}-或-{公厘}-;zh-hk:台灣作-{公釐}-或-{公厘}-;}-。', 'zh-tw'))
|
||||
毫米(毫公分),符號mm,是長度單位和降雨量單位,港澳和大陸稱為毫米(台灣亦有使用,但較常使用名稱為毫公分)。
|
||||
>>> print(convert_for_mw('毫米(毫公分),符號mm,是長度單位和降雨量單位,-{zh-hans:台湾作-{公釐}-或-{公厘}-;zh-hant:港澳和大陸稱為-{毫米}-(台灣亦有使用,但較常使用名稱為毫公分);zh-mo:台灣作-{公釐}-或-{公厘}-;zh-hk:台灣作-{公釐}-或-{公厘}-;}-。', 'zh-cn'))
|
||||
毫米(毫公分),符号mm,是长度单位和降雨量单位,台湾作公釐或公厘。
|
||||
>>> print(convert_for_mw('毫米(毫公分),符號mm,是長度單位和降雨量單位,-{zh-hans:台湾作-{公釐}-或-{公厘}-;zh-hant:港澳和大陸稱為-{毫米}-(台灣亦有使用,但較常使用名稱為毫公分);zh-mo:台灣作-{公釐}-或-{公厘}-;zh-hk:台灣作-{公釐}-或-{公厘', 'zh-hk')) # unbalanced test
|
||||
毫米(毫公分),符號mm,是長度單位和降雨量單位,台灣作公釐或公厘
|
||||
>>> print(convert_for_mw('报头的“-{參攷消息}-”四字摘自鲁迅笔迹-{zh-hans:,“-{參}-”是“-{参}-”的繁体字,读音cān,与简体的“-{参}-”字相同;;zh-hant:,;}-“-{攷}-”是“考”的异体字,读音kǎo,与“考”字相同。', 'zh-tw'))
|
||||
報頭的「參攷消息」四字摘自魯迅筆跡,「攷」是「考」的異體字,讀音kǎo,與「考」字相同。
|
||||
>>> print(convert_for_mw('报头的“-{參攷消息}-”四字摘自鲁迅笔迹-{zh-hans:,“-{參}-”是“-{参}-”的繁体字,读音cān,与简体的“-{参}-”字相同;;zh-hant:,;}-“-{攷}-”是“考”的异体字,读音kǎo,与“考”字相同。', 'zh-cn'))
|
||||
报头的“參攷消息”四字摘自鲁迅笔迹,“參”是“参”的繁体字,读音cān,与简体的“参”字相同;“攷”是“考”的异体字,读音kǎo,与“考”字相同。
|
||||
>>> print(convert_for_mw('{{Col-break}}-->', 'zh-hant'))
|
||||
{{Col-break}}-->
|
||||
"""
|
||||
ch = []
|
||||
rules = []
|
||||
ruledict = update.copy() if update else {}
|
||||
nested = 0
|
||||
block = ''
|
||||
for frag in RE_langconv.split(s):
|
||||
if frag == '-{':
|
||||
nested += 1
|
||||
block += frag
|
||||
elif frag == '}-':
|
||||
if not nested:
|
||||
# bogus }-
|
||||
ch.append(frag)
|
||||
continue
|
||||
block += frag
|
||||
nested -= 1
|
||||
if nested:
|
||||
continue
|
||||
newrules = []
|
||||
delim = RE_splitflag.split(block[2:-2].strip(' \t\n\r\f\v;'))
|
||||
if len(delim) == 1:
|
||||
flag = None
|
||||
mapping = RE_splitmap.split(delim[0])
|
||||
else:
|
||||
flag = RE_splitmap.split(delim[0].strip(' \t\n\r\f\v;'))
|
||||
mapping = RE_splitmap.split(delim[1])
|
||||
rule = {}
|
||||
for m in mapping:
|
||||
uni = RE_splituni.split(m)
|
||||
if len(uni) == 1:
|
||||
pair = RE_splitpair.split(uni[0])
|
||||
else:
|
||||
if rule:
|
||||
newrules.append(rule)
|
||||
rule = {':uni': uni[0]}
|
||||
else:
|
||||
rule[':uni'] = uni[0]
|
||||
pair = RE_splitpair.split(uni[1])
|
||||
if len(pair) == 1:
|
||||
rule['zh'] = convert_for_mw(pair[0], 'zh', ruledict)
|
||||
else:
|
||||
rule[pair[0]] = convert_for_mw(pair[1], pair[0], ruledict)
|
||||
newrules.append(rule)
|
||||
if not flag:
|
||||
ch.append(fallback(locale, newrules[0]))
|
||||
elif any(ch in flag for ch in 'ATRD-HN'):
|
||||
for f in flag:
|
||||
# A: add rule for convert code (all text convert)
|
||||
# H: Insert a conversion rule without output
|
||||
if f in ('A', 'H'):
|
||||
for r in newrules:
|
||||
if not r in rules:
|
||||
rules.append(r)
|
||||
if f == 'A':
|
||||
if ':uni' in r:
|
||||
if locale in r:
|
||||
ch.append(r[locale])
|
||||
else:
|
||||
ch.append(convert(r[':uni'], locale))
|
||||
else:
|
||||
ch.append(fallback(locale, newrules[0]))
|
||||
# -: remove convert
|
||||
elif f == '-':
|
||||
for r in newrules:
|
||||
try:
|
||||
rules.remove(r)
|
||||
except ValueError:
|
||||
pass
|
||||
# D: convert description (useless)
|
||||
#elif f == 'D':
|
||||
#ch.append('; '.join(': '.join(x) for x in newrules[0].items()))
|
||||
# T: title convert (useless)
|
||||
# R: raw content (implied above)
|
||||
# N: current variant name (useless)
|
||||
#elif f == 'N':
|
||||
#ch.append(locale)
|
||||
ruledict = convtable2dict(rules, locale, update)
|
||||
else:
|
||||
fblimit = frozenset(flag) & frozenset(Locales[locale])
|
||||
limitedruledict = update.copy() if update else {}
|
||||
for r in rules:
|
||||
if ':uni' in r:
|
||||
if locale in r:
|
||||
limitedruledict[r[':uni']] = r[locale]
|
||||
else:
|
||||
v = None
|
||||
for l in Locales[locale]:
|
||||
if l in r and l in fblimit:
|
||||
v = r[l]
|
||||
break
|
||||
for word in r.values():
|
||||
limitedruledict[word] = v if v else convert(word, locale)
|
||||
ch.append(convert(delim[1], locale, limitedruledict))
|
||||
block = ''
|
||||
elif nested:
|
||||
block += frag
|
||||
else:
|
||||
ch.append(convert(frag, locale, ruledict))
|
||||
if nested:
|
||||
# unbalanced
|
||||
ch.append(convert_for_mw(block + '}-'*nested, locale, ruledict))
|
||||
return ''.join(ch)
|
||||
|
||||
def test_convert_mw(locale, update=None):
|
||||
s = ('英國-{zh:利兹;zh-hans:利兹;zh-hk:列斯;zh-tw:里茲}-大学\n'
|
||||
'-{zh-hans:计算机; zh-hant:電腦;}-\n'
|
||||
'-{H|巨集=>zh-cn:宏;}-\n'
|
||||
'测试:巨集、宏\n'
|
||||
'-{简体字繁體字}-\n'
|
||||
'北-{}-韓、北朝-{}-鲜\n'
|
||||
'-{H|zh-cn:博客; zh-hk:網誌; zh-tw:部落格;}-\n'
|
||||
'测试:博客、網誌、部落格\n'
|
||||
'-{A|zh-cn:博客; zh-hk:網誌; zh-tw:部落格;}-\n'
|
||||
'测试:博客、網誌、部落格\n'
|
||||
'-{H|zh-cn:博客; zh-hk:網誌; zh-tw:部落格;}-\n'
|
||||
'测试1:博客、網誌、部落格\n'
|
||||
'-{-|zh-cn:博客; zh-hk:網誌; zh-tw:部落格;}-\n'
|
||||
'测试2:博客、網誌、部落格\n'
|
||||
'-{T|zh-cn:汤姆·汉克斯; zh-hk:湯·漢斯; zh-tw:湯姆·漢克斯;}-\n'
|
||||
'-{D|zh-cn:汤姆·汉克斯; zh-hk:湯·漢斯; zh-tw:湯姆·漢克斯;}-\n'
|
||||
'-{H|zh-cn:博客; zh-hk:網誌; zh-tw:部落格;}-\n'
|
||||
'测试1:-{zh;zh-hans;zh-hant|博客、網誌、部落格}-\n'
|
||||
'测试2:-{zh;zh-cn;zh-hk|博客、網誌、部落格}-')
|
||||
return convert_for_mw(s, locale, update)
|
||||
|
||||
def main():
|
||||
"""
|
||||
Simple stdin/stdout interface.
|
||||
"""
|
||||
if len(sys.argv) == 2 and sys.argv[1] in Locales:
|
||||
locale = sys.argv[1]
|
||||
convertfunc = convert
|
||||
elif len(sys.argv) == 3 and sys.argv[1] == '-w' and sys.argv[2] in Locales:
|
||||
locale = sys.argv[2]
|
||||
convertfunc = convert_for_mw
|
||||
else:
|
||||
thisfile = __file__ if __name__ == '__main__' else 'python -mzhconv'
|
||||
print("usage: %s [-w] {zh-cn|zh-tw|zh-hk|zh-sg|zh-hans|zh-hant|zh} < input > output" % thisfile)
|
||||
sys.exit(1)
|
||||
|
||||
loaddict()
|
||||
ln = sys.stdin.readline()
|
||||
while ln:
|
||||
l = ln.rstrip('\r\n')
|
||||
if sys.version_info[0] < 3:
|
||||
l = unicode(l, 'utf-8')
|
||||
res = convertfunc(l, locale)
|
||||
if sys.version_info[0] < 3:
|
||||
print(res.encode('utf-8'))
|
||||
else:
|
||||
print(res)
|
||||
ln = sys.stdin.readline()
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(convert('我幹什麼不干你事。', 'zh-cn'))
|
||||
2
static/dist/css/app.css
vendored
2
static/dist/css/app.css
vendored
File diff suppressed because one or more lines are too long
2
static/dist/index.prod.html
vendored
2
static/dist/index.prod.html
vendored
@@ -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/favicon_64.ico 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.7bcec15980457eb0d5ae.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.44031ebb69b7019db6af.js></script></body></html>
|
||||
1
static/dist/js/app.7bcec15980457eb0d5ae.js
vendored
1
static/dist/js/app.7bcec15980457eb0d5ae.js
vendored
File diff suppressed because one or more lines are too long
@@ -36,5 +36,8 @@ export default {
|
||||
},
|
||||
translationLyc: function(params) {
|
||||
return POST(reUrl + '/api/translation_lyc/', params)
|
||||
},
|
||||
getRecord: function(params) {
|
||||
return GET(reUrl + '/api/record/', params)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<bk-popover theme="light navigation-message" :arrow="false" offset="-150, 5" trigger="mouseenter"
|
||||
:tippy-options="{ 'hideOnClick': false }">
|
||||
<div class="header-mind">
|
||||
<bk-badge class="" :theme="'danger'" :max="99" :val="msgList.length" :visible="msgList.length > 0">
|
||||
<svg style="width: 1em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;"
|
||||
viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
@@ -20,15 +21,16 @@
|
||||
<path
|
||||
d="M53.8,49.1L50,41.5V28c0-8.4-5.8-15.7-14-17.6V8c0-2.2-1.8-4-4-4s-4,1.8-4,4v2.4c-8.2,1.9-14,9.2-14,17.6v13.5l-3.8,7.6c-0.3,0.6-0.3,1.3,0.1,1.9c0.4,0.6,1,1,1.7,1h40c0.7,0,1.3-0.4,1.7-1C54,50.4,54.1,49.7,53.8,49.1z"></path>
|
||||
</svg>
|
||||
<span class="header-mind-mark"></span>
|
||||
</bk-badge>
|
||||
</div>
|
||||
<template slot="content">
|
||||
<div class="monitor-navigation-message">
|
||||
<h5 class="message-title">消息中心</h5>
|
||||
<ul class="message-list">
|
||||
<li class="message-list-item" v-for="(item,index) in msgList" :key="index">
|
||||
<li class="message-list-item" v-for="(item,index) in msgList" :key="index"
|
||||
@click="handleRedirect(item)">
|
||||
<span class="item-message">{{ item.message }}</span>
|
||||
<span class="item-date">{{ item.date }}</span>
|
||||
<span class="item-date">{{ item.created_at }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -43,7 +45,7 @@
|
||||
<template slot="content">
|
||||
<ul class="monitor-navigation-admin">
|
||||
<li class="nav-item" @click="handleUserListClic2k">
|
||||
后台管理
|
||||
后台管理{{refresh}}
|
||||
</li>
|
||||
<li class="nav-item" @click="handleUserListClic3k">
|
||||
使用手册
|
||||
@@ -61,6 +63,7 @@
|
||||
|
||||
<script>
|
||||
import {clearStore} from '../../../common/store.js'
|
||||
import {mapGetters} from 'vuex'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -68,8 +71,7 @@
|
||||
logout_url: 'https://github.com/xhongc/music-tag-web',
|
||||
pageTitle: '测试',
|
||||
userData: {},
|
||||
msgList: [
|
||||
],
|
||||
msgList: [],
|
||||
user: {
|
||||
list: [
|
||||
'关于作者'
|
||||
@@ -78,16 +80,28 @@
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['getHasMsg']),
|
||||
refresh() {
|
||||
if (this.getHasMsg) {
|
||||
this.$store.commit('setHasMsg', false)
|
||||
this.fetchRecord()
|
||||
}
|
||||
},
|
||||
headerTitle() {
|
||||
return this.$route.meta.title
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loginUser()
|
||||
this.fetchRecord()
|
||||
},
|
||||
methods: {
|
||||
changeTitle() {
|
||||
},
|
||||
handleRedirect(item) {
|
||||
console.log(item.parent_path)
|
||||
this.$store.commit('setFullPath', item.parent_path)
|
||||
},
|
||||
handleUserListClick(e) {
|
||||
const btn = document.createElement('a')
|
||||
btn.setAttribute('href', this.logout_url)
|
||||
@@ -121,7 +135,14 @@
|
||||
},
|
||||
handleBack() {
|
||||
this.$router.go(-1)
|
||||
}
|
||||
},
|
||||
fetchRecord() {
|
||||
this.$api.Task.getRecord({'state': 'failed', 'page_size': 20}).then((res) => {
|
||||
if (res.result) {
|
||||
this.msgList = res.data.items
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -75,7 +75,9 @@
|
||||
</div>
|
||||
<div v-for="(item, index) in showFields" :key="'l1' + index">
|
||||
<div class="edit-item" v-if="item === 'filename'">
|
||||
<div class="label1 can-copy" v-bk-tooltips="'变量名:${filename}'" v-bk-copy="'${filename}'">文件名:</div>
|
||||
<div class="label1 can-copy" v-bk-tooltips="'变量名:${filename}'" v-bk-copy="'${filename}'">
|
||||
文件名:
|
||||
</div>
|
||||
<div style="width: 70%;">
|
||||
<bk-input :clearable="true" v-model="musicInfo.filename"></bk-input>
|
||||
</div>
|
||||
@@ -93,7 +95,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit-item can-copy" v-else-if="item === 'albumartist'">
|
||||
<div class="label1" v-bk-tooltips="'变量名:${albumartist}'" v-bk-copy="'${albumartist}'">专辑艺术家:</div>
|
||||
<div class="label1" v-bk-tooltips="'变量名:${albumartist}'" v-bk-copy="'${albumartist}'">
|
||||
专辑艺术家:
|
||||
</div>
|
||||
<div style="width: 70%;">
|
||||
<bk-input :clearable="true" v-model="musicInfo.albumartist"></bk-input>
|
||||
</div>
|
||||
@@ -263,14 +267,17 @@
|
||||
</div>
|
||||
<div v-for="(item, index) in showFields" :key="'l2' + index">
|
||||
<div class="edit-item" v-if="item === 'filename'">
|
||||
<div class="label1 can-copy" v-bk-tooltips="'变量名:${filename}'" v-bk-copy="'${filename}'">文件名:</div>
|
||||
<div class="label1 can-copy" v-bk-tooltips="'变量名:${filename}'" v-bk-copy="'${filename}'">
|
||||
文件名:
|
||||
</div>
|
||||
<div style="width: 70%;">
|
||||
<bk-input :clearable="true" v-model="musicInfoManual.filename"
|
||||
:placeholder="'例如:${title}-${album}'"></bk-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit-item" v-else-if="item === 'artist'">
|
||||
<div class="label1 can-copy" v-bk-tooltips="'变量名:${artist}'" v-bk-copy="'${artist}'">艺术家:</div>
|
||||
<div class="label1 can-copy" v-bk-tooltips="'变量名:${artist}'" v-bk-copy="'${artist}'">艺术家:
|
||||
</div>
|
||||
<div style="width: 70%;">
|
||||
<bk-input :clearable="true" v-model="musicInfoManual.artist"
|
||||
:placeholder="'具体哪些变量,鼠标悬浮在标题上查看'"></bk-input>
|
||||
@@ -283,7 +290,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="edit-item" v-else-if="item === 'albumartist'">
|
||||
<div class="label1 can-copy" v-bk-tooltips="'变量名:${albumartist}'" v-bk-copy="'${albumartist}'">专辑艺术家:</div>
|
||||
<div class="label1 can-copy" v-bk-tooltips="'变量名:${albumartist}'"
|
||||
v-bk-copy="'${albumartist}'">专辑艺术家:
|
||||
</div>
|
||||
<div style="width: 70%;">
|
||||
<bk-input :clearable="true" v-model="musicInfoManual.albumartist"></bk-input>
|
||||
</div>
|
||||
@@ -566,6 +575,8 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {mapGetters} from 'vuex'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -600,7 +611,6 @@
|
||||
],
|
||||
searchWord: '',
|
||||
treeListOne: [],
|
||||
filePath: '/app/media/',
|
||||
fullPath: '',
|
||||
fileName: '',
|
||||
resource: localStorage.getItem('resource') ? localStorage.getItem('resource') : 'netease',
|
||||
@@ -700,6 +710,27 @@
|
||||
sortedField: localStorage.getItem('sortedField') ? JSON.parse(localStorage.getItem('sortedField')) : []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['geFullPath']),
|
||||
filePath: {
|
||||
get() {
|
||||
if (this.geFullPath) {
|
||||
console.log(this.geFullPath)
|
||||
const fullPath = this.geFullPath
|
||||
this.$store.commit('setFullPath', '')
|
||||
this.$nextTick(() => {
|
||||
this.handleSearchFile()
|
||||
})
|
||||
return fullPath
|
||||
} else {
|
||||
return '/app/media/'
|
||||
}
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('setFullPath', value)
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.handleSearchFile()
|
||||
},
|
||||
@@ -729,7 +760,7 @@
|
||||
},
|
||||
backDir() {
|
||||
this.filePath = this.backPath(this.filePath)
|
||||
this.handleSearchFile()
|
||||
// this.handleSearchFile()
|
||||
},
|
||||
backPath(path) {
|
||||
// 使用正则表达式匹配最后一个斜杠及其后面的内容
|
||||
@@ -751,7 +782,7 @@
|
||||
nodeClickOne(node) {
|
||||
if (node.icon === 'icon-folder') {
|
||||
this.filePath = this.filePath + '/' + node.name
|
||||
this.handleSearchFile()
|
||||
// this.handleSearchFile()
|
||||
} else {
|
||||
if (node.children && node.children.length > 0) {
|
||||
return
|
||||
@@ -907,6 +938,7 @@
|
||||
this.isLoading = false
|
||||
if (res.result) {
|
||||
this.$cwMessage('修改成功', 'success')
|
||||
this.$store.commit('setHasMsg', true)
|
||||
} else {
|
||||
this.$cwMessage('修改失败', 'error')
|
||||
}
|
||||
@@ -957,6 +989,7 @@
|
||||
console.log(res)
|
||||
if (res.result) {
|
||||
this.$cwMessage('创建成功', 'success')
|
||||
this.$store.commit('setHasMsg', true)
|
||||
}
|
||||
})
|
||||
return true
|
||||
@@ -1219,6 +1252,7 @@ button.bk-button-text {
|
||||
margin-bottom: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.can-copy {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
const getters = {
|
||||
getUserRole: state => state.common.userRole // 权限
|
||||
getUserRole: state => state.common.userRole,
|
||||
geFullPath: state => state.common.fullPath,
|
||||
getHasMsg: state => state.common.hasMsg
|
||||
}
|
||||
export default getters
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
const common = {
|
||||
state: {
|
||||
defaultTableHeight: 800,
|
||||
userRole: ''
|
||||
userRole: '',
|
||||
fullPath: '',
|
||||
hasMsg: false
|
||||
},
|
||||
mutations: {
|
||||
setDefaultTableHeight: (state, val) => {
|
||||
@@ -9,6 +11,12 @@ const common = {
|
||||
},
|
||||
setUserRole: (state, val) => {
|
||||
state.userRole = val
|
||||
},
|
||||
setFullPath: (state, val) => {
|
||||
state.fullPath = val
|
||||
},
|
||||
setHasMsg: (state, val) => {
|
||||
state.hasMsg = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user