feature:支持翻译

This commit is contained in:
charlesxie
2023-07-28 11:06:15 +08:00
parent f30aaeec0e
commit 6d7862b7c0
11 changed files with 244 additions and 12 deletions

View File

@@ -28,11 +28,11 @@ DFF等音频格式。
### 从阿里云Docker Registry拉取镜像
1`docker pull registry.cn-hangzhou.aliyuncs.com/charles0519/music_tag_web:1.0.9`
1`docker pull registry.cn-hangzhou.aliyuncs.com/charles0519/music_tag_web:1.1.0`
### dokcer run
2. `docker run -d -p 8001:8001 -v /path/to/your/music:/app/media --restart=always registry.cn-hangzhou.aliyuncs.com/charles0519/music_tag_web:1.0.9`
2. `docker run -d -p 8001:8001 -v /path/to/your/music:/app/media --restart=always registry.cn-hangzhou.aliyuncs.com/charles0519/music_tag_web:1.1.0`
或者 使用portainer stacks部署
![img_1.png](img_1.png)
@@ -42,7 +42,7 @@ version: '3'
services:
music-tag:
image: registry.cn-hangzhou.aliyuncs.com/charles0519/music_tag_web:1.0.9
image: registry.cn-hangzhou.aliyuncs.com/charles0519/music_tag_web:1.1.0
container_name: music-tag-web
ports:
- "8001:8001"

View File

@@ -44,3 +44,7 @@ class FetchId3ByTitleSerializer(serializers.Serializer):
class FetchLlyricSerializer(serializers.Serializer):
song_id = serializers.CharField(required=True)
resource = serializers.CharField(required=True)
class TranslationLycSerializer(serializers.Serializer):
lyc = serializers.CharField(required=True)

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
FetchId3ByTitleSerializer, FetchLlyricSerializer, BatchUpdateId3Serializer, TranslationLycSerializer
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.utils.translation import translation_lyc_text
from component.drf.viewsets import GenericViewSet
from django_vue_cli.celery_app import app as celery_app
@@ -33,6 +34,8 @@ class TaskViewSets(GenericViewSet):
return FetchLlyricSerializer
elif self.action in ["batch_update_id3", "batch_auto_update_id3"]:
return BatchUpdateId3Serializer
elif self.action == "translation_lyc":
return TranslationLycSerializer
return FileListSerializer
@action(methods=['POST'], detail=False)
@@ -46,7 +49,7 @@ class TaskViewSets(GenericViewSet):
except FileNotFoundError:
return self.failure_response(msg="文件夹不存在")
children_data = []
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"]
frc_map = {}
file_data = []
@@ -154,7 +157,7 @@ class TaskViewSets(GenericViewSet):
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",
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
@@ -220,6 +223,40 @@ class TaskViewSets(GenericViewSet):
songs = MusicResource(resource).fetch_id3_by_title(title)
return self.success_response(data=songs)
@action(methods=['POST'], detail=False)
def translation_lyc(self, request, *args, **kwargs):
validate_data = self.is_validated_data(request.data)
lyc = validate_data["lyc"]
clean_lyc_list = []
raw_lyc_list = []
for line in lyc.split("\n"):
if not line:
continue
clean_line = line.split("]")[-1]
clean_line = clean_line.strip()
if not clean_line:
continue
raw_lyc_list.append(line)
clean_lyc_list.append(clean_line)
clean_lyc_str = "\n".join(clean_lyc_list)
results = translation_lyc_text(clean_lyc_str)
new_lyc = []
results_list = results.split("\n")
for index, result in enumerate(results_list):
if not result:
new_lyc.append(raw_lyc_list[index])
else:
try:
src = clean_lyc_list[index]
raw_src = raw_lyc_list[index]
except Exception as e:
continue
if src.replace(" ", "") == result.replace(" ", ""):
new_lyc.append(raw_src)
else:
new_lyc.append(f"{raw_src}\n{result}\n")
return self.success_response(data="\n".join(new_lyc))
@action(methods=["get"], detail=False)
def clear_celery(self, request, *args, **kwargs):
active_tasks = celery_app.control.inspect().active()

View File

@@ -0,0 +1,39 @@
import hashlib
import random
import time
import requests
import translators as ts
def translation_lyc_text(contents):
if len(contents) > 1000:
results = []
contents = contents.split('\n')
content_1k = ""
for each in contents:
if len(content_1k + each + '\n') > 1000:
results.append(content_1k)
content_1k = each + '\n'
else:
content_1k += each + '\n'
if content_1k:
results.append(content_1k)
translate_res = ""
for content in results:
res = ts.translate_text(content, translator="youdao", to_language="zh-CHS")
if translate_res:
translate_res += "\n" + res
else:
translate_res = res
return translate_res
else:
res = ts.translate_text(contents, translator="youdao", to_language="zh-CHS")
print(res)
return res
if __name__ == '__main__':
a = translation_lyc_text('スピード')
print(a)

View File

View File

@@ -0,0 +1,110 @@
# import asyncio
#
# import aiohttp
# from django.conf import settings
#
# concurrence_limit = 5
#
#
# def callback(future): # 这里默认传入一个future对象
# pass
#
#
# class AsyncRequest:
# """
# 协程请求
# """
#
# # 限制同时打开的连接的数量 limit=0为不限制
# limit = concurrence_limit
# timeout = 60
# raise_for_status = False
#
# session = None
# result = {}
#
# @classmethod
# def run(cls, main=None):
# loop = asyncio.new_event_loop()
# try:
# asyncio.set_event_loop(loop)
# asyncio.get_event_loop().run_until_complete(main)
# finally:
# try:
# if hasattr(loop, "shutdown_asyncgens"):
# loop.run_until_complete(loop.shutdown_asyncgens())
# finally:
# asyncio.set_event_loop(None)
# loop.close()
#
# @classmethod
# async def async_request(cls, method, url_data_map, headers=None):
# """
# method : get,post
# """
# headers = headers or {}
# conn = aiohttp.TCPConnector(limit=cls.limit, verify_ssl=False)
# async with aiohttp.ClientSession(connector=conn, headers=headers) as session:
# cls.session = session
# result_list = await cls.fetch_multi(url_data_map, method=method)
# return result_list
#
# @classmethod
# def _request(cls, method, url=None, data_list=None, url_data_map=None, headers=None):
# assert url or url_data_map
# is_simple = False
# if not url_data_map:
# data_list = data_list or []
# url_data_map = {url: data_list}
# is_simple = True
# main = cls.async_request(method, url_data_map, headers=headers)
# cls.run(main)
# if is_simple:
# return cls.result.get(url, [])
# else:
# return cls.result
#
# @classmethod
# def get(cls, url=None, data_list=None, url_data_map=None, headers=None):
# """
# get请求
# example: AsyncRequest.get('url', [{'params': i} for i in range(30)])
# AsyncRequest.get(url_data_map={'url':[{'params': i} for i in range(30)]})
#
# :param url: 请求地址, 与url_data_map只需传一种
# :param data_list: 参数列表 [{},{}]
# :param url_data_map: url和data_list的映射表
# :return: 结果集
# """
# return cls._request("get", url, data_list, url_data_map, headers)
#
# @classmethod
# def post(cls, url=None, data_list=None, url_data_map=None, headers=None):
# return cls._request("post", url, data_list, url_data_map, headers)
#
# @classmethod
# async def fetch(cls, url, data, method, index):
# post_data = [{"params": data}, {"data": data}][method == "post"]
# try:
# async with getattr(cls.session, method)(
# url, **post_data, timeout=cls.timeout, raise_for_status=cls.raise_for_status
# ) as resp:
# ret_data = await resp.json()
# cls.result[url][index] = ret_data
# return ret_data
# except Exception as e:
# print("asd", e)
#
# @classmethod
# async def fetch_multi(cls, url_data_map, method):
# tasks = []
# for url, data_list in url_data_map.items():
# cls.result[url] = [None] * len(data_list)
# for index, data in enumerate(data_list):
# task = asyncio.ensure_future(cls.fetch(url, data, method, index))
# task.add_done_callback(callback)
# tasks.append(task)
#
# # gather: 搜集所有future对象并等待返回
# results = await asyncio.wait(tasks)
# return results

View File

@@ -19,4 +19,5 @@ redis==3.2.0
mysqlclient==1.4.4
sqlalchemy==1.4.23
PyExecJS==1.5.1
translators==5.8.0

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.95d1ec148ea3ea870662.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.f9f7f7356e09293abe67.js></script></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -30,5 +30,8 @@ export default {
},
fetchLyric: function(params) {
return POST(reUrl + '/api/fetch_lyric/', params)
},
translationLyc: function(params) {
return POST(reUrl + '/api/translation_lyc/', params)
}
}

View File

@@ -61,7 +61,8 @@
</div>
<div>
<bk-icon type="arrows-right-shape" @click="toggleLock('title')"
style="cursor: pointer;color: #64c864;margin-left: 20px;"></bk-icon>
style="cursor: pointer;color: #64c864;margin-left: 20px;">
</bk-icon>
</div>
</div>
<div style="display: flex;margin-bottom: 10px;align-items: center;">
@@ -117,8 +118,13 @@
<div style="display: flex;">
<div class="label1">歌词</div>
<div style="width: 70%;">
<bk-input :clearable="true" v-model="musicInfo.lyrics" type="textarea" :rows="15"
></bk-input>
<bk-input :clearable="true" v-model="musicInfo.lyrics" type="textarea" :rows="15">
</bk-input>
</div>
<div>
<bk-icon type="arrows-right-shape" @click="translation()"
style="cursor: pointer;color: #64c864;margin-left: 20px;">
</bk-icon>
</div>
</div>
<div style="display: flex;margin-top: 10px;">
@@ -308,7 +314,21 @@
</div>
</div>
</transition>
<div v-show="!fadeShowDetail" style="width: 90%;height: 90%; margin: 50px 20px 20px 50px;">
<transition name="bk-slide-fade-left">
<div v-show="showTranslation">
<div style="display: flex;height: 100%;">
<bk-icon type="arrows-left-shape" @click="handleCopy('lyric_tran',translationText)"
style="margin-right: 5px;margin-left: 15px;margin-top: 50%;cursor: pointer;"></bk-icon>
<div style="width: 100%;height: 100%;">
<bk-input :clearable="true" v-model="translationText" type="textarea" :rows="50"
style="height: 100%;">
</bk-input>
</div>
</div>
</div>
</transition>
<div v-show="!fadeShowDetail && !showTranslation"
style="width: 90%;height: 90%; margin: 50px 20px 20px 50px;">
<bk-image fit="contain" :src="'/static/dist/img/music_null-cutout.png'"
style="width: 100%;height: 98%;"></bk-image>
</div>
@@ -354,6 +374,7 @@
fullPath: '',
fileName: '',
resource: 'netease',
translationText: '',
resourceList: [
{id: 'acoustid', name: '音乐指纹识别'},
{id: 'netease', name: '网易云音乐'},
@@ -376,6 +397,7 @@
},
fadeShowDir: false,
fadeShowDetail: false,
showTranslation: false,
isLoading: false,
SongList: [],
reloadImg: true,
@@ -520,6 +542,9 @@
this.$nextTick(() => {
this.reloadImg = true
})
} else if (k === 'lyric_tran') {
console.log(v)
this.musicInfo['lyrics'] = v
} else {
this.musicInfo[k] = v
}
@@ -541,6 +566,7 @@
this.$cwMessage('标题不能为空', 'error')
return
}
this.showTranslation = false
this.fadeShowDetail = false
this.$api.Task.fetchId3Title({
title: this.musicInfo.title,
@@ -552,6 +578,19 @@
})
}
},
translation() {
if (!this.musicInfo.lyrics) {
this.$cwMessage('歌词不能为空', 'error')
}
this.fadeShowDetail = false
this.showTranslation = true
this.$api.Task.translationLyc({
lyc: this.musicInfo.lyrics
}).then((res) => {
this.showTranslation = true
this.translationText = res.data
})
},
// 文件目录
handleSearchFile() {
this.fadeShowDir = false