init (again)

This commit is contained in:
2026-05-26 15:18:17 +02:00
commit 22246060e6
270 changed files with 117698 additions and 0 deletions
+491
View File
@@ -0,0 +1,491 @@
-- modified from https://github.com/idiomic/Lua_AES
--[[
Copyright 2019 Tyler Richard Hoyer
Copyright 2025 dyphire
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]
local unpack = unpack or table.unpack
local GF8x2 = {
[0]=0x00,0x02,0x04,0x06,0x08,0x0a,0x0c,0x0e,0x10,0x12,0x14,0x16,0x18,0x1a,0x1c,0x1e,
0x20,0x22,0x24,0x26,0x28,0x2a,0x2c,0x2e,0x30,0x32,0x34,0x36,0x38,0x3a,0x3c,0x3e,
0x40,0x42,0x44,0x46,0x48,0x4a,0x4c,0x4e,0x50,0x52,0x54,0x56,0x58,0x5a,0x5c,0x5e,
0x60,0x62,0x64,0x66,0x68,0x6a,0x6c,0x6e,0x70,0x72,0x74,0x76,0x78,0x7a,0x7c,0x7e,
0x80,0x82,0x84,0x86,0x88,0x8a,0x8c,0x8e,0x90,0x92,0x94,0x96,0x98,0x9a,0x9c,0x9e,
0xa0,0xa2,0xa4,0xa6,0xa8,0xaa,0xac,0xae,0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbc,0xbe,
0xc0,0xc2,0xc4,0xc6,0xc8,0xca,0xcc,0xce,0xd0,0xd2,0xd4,0xd6,0xd8,0xda,0xdc,0xde,
0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xee,0xf0,0xf2,0xf4,0xf6,0xf8,0xfa,0xfc,0xfe,
0x1b,0x19,0x1f,0x1d,0x13,0x11,0x17,0x15,0x0b,0x09,0x0f,0x0d,0x03,0x01,0x07,0x05,
0x3b,0x39,0x3f,0x3d,0x33,0x31,0x37,0x35,0x2b,0x29,0x2f,0x2d,0x23,0x21,0x27,0x25,
0x5b,0x59,0x5f,0x5d,0x53,0x51,0x57,0x55,0x4b,0x49,0x4f,0x4d,0x43,0x41,0x47,0x45,
0x7b,0x79,0x7f,0x7d,0x73,0x71,0x77,0x75,0x6b,0x69,0x6f,0x6d,0x63,0x61,0x67,0x65,
0x9b,0x99,0x9f,0x9d,0x93,0x91,0x97,0x95,0x8b,0x89,0x8f,0x8d,0x83,0x81,0x87,0x85,
0xbb,0xb9,0xbf,0xbd,0xb3,0xb1,0xb7,0xb5,0xab,0xa9,0xaf,0xad,0xa3,0xa1,0xa7,0xa5,
0xdb,0xd9,0xdf,0xdd,0xd3,0xd1,0xd7,0xd5,0xcb,0xc9,0xcf,0xcd,0xc3,0xc1,0xc7,0xc5,
0xfb,0xf9,0xff,0xfd,0xf3,0xf1,0xf7,0xf5,0xeb,0xe9,0xef,0xed,0xe3,0xe1,0xe7,0xe5
}
local GF8x3 = {
[0]=0x00,0x03,0x06,0x05,0x0c,0x0f,0x0a,0x09,0x18,0x1b,0x1e,0x1d,0x14,0x17,0x12,0x11,
0x30,0x33,0x36,0x35,0x3c,0x3f,0x3a,0x39,0x28,0x2b,0x2e,0x2d,0x24,0x27,0x22,0x21,
0x60,0x63,0x66,0x65,0x6c,0x6f,0x6a,0x69,0x78,0x7b,0x7e,0x7d,0x74,0x77,0x72,0x71,
0x50,0x53,0x56,0x55,0x5c,0x5f,0x5a,0x59,0x48,0x4b,0x4e,0x4d,0x44,0x47,0x42,0x41,
0xc0,0xc3,0xc6,0xc5,0xcc,0xcf,0xca,0xc9,0xd8,0xdb,0xde,0xdd,0xd4,0xd7,0xd2,0xd1,
0xf0,0xf3,0xf6,0xf5,0xfc,0xff,0xfa,0xf9,0xe8,0xeb,0xee,0xed,0xe4,0xe7,0xe2,0xe1,
0xa0,0xa3,0xa6,0xa5,0xac,0xaf,0xaa,0xa9,0xb8,0xbb,0xbe,0xbd,0xb4,0xb7,0xb2,0xb1,
0x90,0x93,0x96,0x95,0x9c,0x9f,0x9a,0x99,0x88,0x8b,0x8e,0x8d,0x84,0x87,0x82,0x81,
0x9b,0x98,0x9d,0x9e,0x97,0x94,0x91,0x92,0x83,0x80,0x85,0x86,0x8f,0x8c,0x89,0x8a,
0xab,0xa8,0xad,0xae,0xa7,0xa4,0xa1,0xa2,0xb3,0xb0,0xb5,0xb6,0xbf,0xbc,0xb9,0xba,
0xfb,0xf8,0xfd,0xfe,0xf7,0xf4,0xf1,0xf2,0xe3,0xe0,0xe5,0xe6,0xef,0xec,0xe9,0xea,
0xcb,0xc8,0xcd,0xce,0xc7,0xc4,0xc1,0xc2,0xd3,0xd0,0xd5,0xd6,0xdf,0xdc,0xd9,0xda,
0x5b,0x58,0x5d,0x5e,0x57,0x54,0x51,0x52,0x43,0x40,0x45,0x46,0x4f,0x4c,0x49,0x4a,
0x6b,0x68,0x6d,0x6e,0x67,0x64,0x61,0x62,0x73,0x70,0x75,0x76,0x7f,0x7c,0x79,0x7a,
0x3b,0x38,0x3d,0x3e,0x37,0x34,0x31,0x32,0x23,0x20,0x25,0x26,0x2f,0x2c,0x29,0x2a,
0x0b,0x08,0x0d,0x0e,0x07,0x04,0x01,0x02,0x13,0x10,0x15,0x16,0x1f,0x1c,0x19,0x1a
}
local GF8x9 = {
[0]=0x00,0x09,0x12,0x1b,0x24,0x2d,0x36,0x3f,0x48,0x41,0x5a,0x53,0x6c,0x65,0x7e,0x77,
0x90,0x99,0x82,0x8b,0xb4,0xbd,0xa6,0xaf,0xd8,0xd1,0xca,0xc3,0xfc,0xf5,0xee,0xe7,
0x3b,0x32,0x29,0x20,0x1f,0x16,0x0d,0x04,0x73,0x7a,0x61,0x68,0x57,0x5e,0x45,0x4c,
0xab,0xa2,0xb9,0xb0,0x8f,0x86,0x9d,0x94,0xe3,0xea,0xf1,0xf8,0xc7,0xce,0xd5,0xdc,
0x76,0x7f,0x64,0x6d,0x52,0x5b,0x40,0x49,0x3e,0x37,0x2c,0x25,0x1a,0x13,0x08,0x01,
0xe6,0xef,0xf4,0xfd,0xc2,0xcb,0xd0,0xd9,0xae,0xa7,0xbc,0xb5,0x8a,0x83,0x98,0x91,
0x4d,0x44,0x5f,0x56,0x69,0x60,0x7b,0x72,0x05,0x0c,0x17,0x1e,0x21,0x28,0x33,0x3a,
0xdd,0xd4,0xcf,0xc6,0xf9,0xf0,0xeb,0xe2,0x95,0x9c,0x87,0x8e,0xb1,0xb8,0xa3,0xaa,
0xec,0xe5,0xfe,0xf7,0xc8,0xc1,0xda,0xd3,0xa4,0xad,0xb6,0xbf,0x80,0x89,0x92,0x9b,
0x7c,0x75,0x6e,0x67,0x58,0x51,0x4a,0x43,0x34,0x3d,0x26,0x2f,0x10,0x19,0x02,0x0b,
0xd7,0xde,0xc5,0xcc,0xf3,0xfa,0xe1,0xe8,0x9f,0x96,0x8d,0x84,0xbb,0xb2,0xa9,0xa0,
0x47,0x4e,0x55,0x5c,0x63,0x6a,0x71,0x78,0x0f,0x06,0x1d,0x14,0x2b,0x22,0x39,0x30,
0x9a,0x93,0x88,0x81,0xbe,0xb7,0xac,0xa5,0xd2,0xdb,0xc0,0xc9,0xf6,0xff,0xe4,0xed,
0x0a,0x03,0x18,0x11,0x2e,0x27,0x3c,0x35,0x42,0x4b,0x50,0x59,0x66,0x6f,0x74,0x7d,
0xa1,0xa8,0xb3,0xba,0x85,0x8c,0x97,0x9e,0xe9,0xe0,0xfb,0xf2,0xcd,0xc4,0xdf,0xd6,
0x31,0x38,0x23,0x2a,0x15,0x1c,0x07,0x0e,0x79,0x70,0x6b,0x62,0x5d,0x54,0x4f,0x46
}
local GF8x11 = {
[0]=0x00,0x0b,0x16,0x1d,0x2c,0x27,0x3a,0x31,0x58,0x53,0x4e,0x45,0x74,0x7f,0x62,0x69,
0xb0,0xbb,0xa6,0xad,0x9c,0x97,0x8a,0x81,0xe8,0xe3,0xfe,0xf5,0xc4,0xcf,0xd2,0xd9,
0x7b,0x70,0x6d,0x66,0x57,0x5c,0x41,0x4a,0x23,0x28,0x35,0x3e,0x0f,0x04,0x19,0x12,
0xcb,0xc0,0xdd,0xd6,0xe7,0xec,0xf1,0xfa,0x93,0x98,0x85,0x8e,0xbf,0xb4,0xa9,0xa2,
0xf6,0xfd,0xe0,0xeb,0xda,0xd1,0xcc,0xc7,0xae,0xa5,0xb8,0xb3,0x82,0x89,0x94,0x9f,
0x46,0x4d,0x50,0x5b,0x6a,0x61,0x7c,0x77,0x1e,0x15,0x08,0x03,0x32,0x39,0x24,0x2f,
0x8d,0x86,0x9b,0x90,0xa1,0xaa,0xb7,0xbc,0xd5,0xde,0xc3,0xc8,0xf9,0xf2,0xef,0xe4,
0x3d,0x36,0x2b,0x20,0x11,0x1a,0x07,0x0c,0x65,0x6e,0x73,0x78,0x49,0x42,0x5f,0x54,
0xf7,0xfc,0xe1,0xea,0xdb,0xd0,0xcd,0xc6,0xaf,0xa4,0xb9,0xb2,0x83,0x88,0x95,0x9e,
0x47,0x4c,0x51,0x5a,0x6b,0x60,0x7d,0x76,0x1f,0x14,0x09,0x02,0x33,0x38,0x25,0x2e,
0x8c,0x87,0x9a,0x91,0xa0,0xab,0xb6,0xbd,0xd4,0xdf,0xc2,0xc9,0xf8,0xf3,0xee,0xe5,
0x3c,0x37,0x2a,0x21,0x10,0x1b,0x06,0x0d,0x64,0x6f,0x72,0x79,0x48,0x43,0x5e,0x55,
0x01,0x0a,0x17,0x1c,0x2d,0x26,0x3b,0x30,0x59,0x52,0x4f,0x44,0x75,0x7e,0x63,0x68,
0xb1,0xba,0xa7,0xac,0x9d,0x96,0x8b,0x80,0xe9,0xe2,0xff,0xf4,0xc5,0xce,0xd3,0xd8,
0x7a,0x71,0x6c,0x67,0x56,0x5d,0x40,0x4b,0x22,0x29,0x34,0x3f,0x0e,0x05,0x18,0x13,
0xca,0xc1,0xdc,0xd7,0xe6,0xed,0xf0,0xfb,0x92,0x99,0x84,0x8f,0xbe,0xb5,0xa8,0xa3
}
local GF8x13 = {
[0]=0x00,0x0d,0x1a,0x17,0x34,0x39,0x2e,0x23,0x68,0x65,0x72,0x7f,0x5c,0x51,0x46,0x4b,
0xd0,0xdd,0xca,0xc7,0xe4,0xe9,0xfe,0xf3,0xb8,0xb5,0xa2,0xaf,0x8c,0x81,0x96,0x9b,
0xbb,0xb6,0xa1,0xac,0x8f,0x82,0x95,0x98,0xd3,0xde,0xc9,0xc4,0xe7,0xea,0xfd,0xf0,
0x6b,0x66,0x71,0x7c,0x5f,0x52,0x45,0x48,0x03,0x0e,0x19,0x14,0x37,0x3a,0x2d,0x20,
0x6d,0x60,0x77,0x7a,0x59,0x54,0x43,0x4e,0x05,0x08,0x1f,0x12,0x31,0x3c,0x2b,0x26,
0xbd,0xb0,0xa7,0xaa,0x89,0x84,0x93,0x9e,0xd5,0xd8,0xcf,0xc2,0xe1,0xec,0xfb,0xf6,
0xd6,0xdb,0xcc,0xc1,0xe2,0xef,0xf8,0xf5,0xbe,0xb3,0xa4,0xa9,0x8a,0x87,0x90,0x9d,
0x06,0x0b,0x1c,0x11,0x32,0x3f,0x28,0x25,0x6e,0x63,0x74,0x79,0x5a,0x57,0x40,0x4d,
0xda,0xd7,0xc0,0xcd,0xee,0xe3,0xf4,0xf9,0xb2,0xbf,0xa8,0xa5,0x86,0x8b,0x9c,0x91,
0x0a,0x07,0x10,0x1d,0x3e,0x33,0x24,0x29,0x62,0x6f,0x78,0x75,0x56,0x5b,0x4c,0x41,
0x61,0x6c,0x7b,0x76,0x55,0x58,0x4f,0x42,0x09,0x04,0x13,0x1e,0x3d,0x30,0x27,0x2a,
0xb1,0xbc,0xab,0xa6,0x85,0x88,0x9f,0x92,0xd9,0xd4,0xc3,0xce,0xed,0xe0,0xf7,0xfa,
0xb7,0xba,0xad,0xa0,0x83,0x8e,0x99,0x94,0xdf,0xd2,0xc5,0xc8,0xeb,0xe6,0xf1,0xfc,
0x67,0x6a,0x7d,0x70,0x53,0x5e,0x49,0x44,0x0f,0x02,0x15,0x18,0x3b,0x36,0x21,0x2c,
0x0c,0x01,0x16,0x1b,0x38,0x35,0x22,0x2f,0x64,0x69,0x7e,0x73,0x50,0x5d,0x4a,0x47,
0xdc,0xd1,0xc6,0xcb,0xe8,0xe5,0xf2,0xff,0xb4,0xb9,0xae,0xa3,0x80,0x8d,0x9a,0x97
}
local GF8x14 = {
[0]=0x00,0x0e,0x1c,0x12,0x38,0x36,0x24,0x2a,0x70,0x7e,0x6c,0x62,0x48,0x46,0x54,0x5a,
0xe0,0xee,0xfc,0xf2,0xd8,0xd6,0xc4,0xca,0x90,0x9e,0x8c,0x82,0xa8,0xa6,0xb4,0xba,
0xdb,0xd5,0xc7,0xc9,0xe3,0xed,0xff,0xf1,0xab,0xa5,0xb7,0xb9,0x93,0x9d,0x8f,0x81,
0x3b,0x35,0x27,0x29,0x03,0x0d,0x1f,0x11,0x4b,0x45,0x57,0x59,0x73,0x7d,0x6f,0x61,
0xad,0xa3,0xb1,0xbf,0x95,0x9b,0x89,0x87,0xdd,0xd3,0xc1,0xcf,0xe5,0xeb,0xf9,0xf7,
0x4d,0x43,0x51,0x5f,0x75,0x7b,0x69,0x67,0x3d,0x33,0x21,0x2f,0x05,0x0b,0x19,0x17,
0x76,0x78,0x6a,0x64,0x4e,0x40,0x52,0x5c,0x06,0x08,0x1a,0x14,0x3e,0x30,0x22,0x2c,
0x96,0x98,0x8a,0x84,0xae,0xa0,0xb2,0xbc,0xe6,0xe8,0xfa,0xf4,0xde,0xd0,0xc2,0xcc,
0x41,0x4f,0x5d,0x53,0x79,0x77,0x65,0x6b,0x31,0x3f,0x2d,0x23,0x09,0x07,0x15,0x1b,
0xa1,0xaf,0xbd,0xb3,0x99,0x97,0x85,0x8b,0xd1,0xdf,0xcd,0xc3,0xe9,0xe7,0xf5,0xfb,
0x9a,0x94,0x86,0x88,0xa2,0xac,0xbe,0xb0,0xea,0xe4,0xf6,0xf8,0xd2,0xdc,0xce,0xc0,
0x7a,0x74,0x66,0x68,0x42,0x4c,0x5e,0x50,0x0a,0x04,0x16,0x18,0x32,0x3c,0x2e,0x20,
0xec,0xe2,0xf0,0xfe,0xd4,0xda,0xc8,0xc6,0x9c,0x92,0x80,0x8e,0xa4,0xaa,0xb8,0xb6,
0x0c,0x02,0x10,0x1e,0x34,0x3a,0x28,0x26,0x7c,0x72,0x60,0x6e,0x44,0x4a,0x58,0x56,
0x37,0x39,0x2b,0x25,0x0f,0x01,0x13,0x1d,0x47,0x49,0x5b,0x55,0x7f,0x71,0x63,0x6d,
0xd7,0xd9,0xcb,0xc5,0xef,0xe1,0xf3,0xfd,0xa7,0xa9,0xbb,0xb5,0x9f,0x91,0x83,0x8d
}
local s = {
[0]=0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16
}
local si = {
[0]=0x52,0x09,0x6A,0xD5,0x30,0x36,0xA5,0x38,0xBF,0x40,0xA3,0x9E,0x81,0xF3,0xD7,0xFB,
0x7C,0xE3,0x39,0x82,0x9B,0x2F,0xFF,0x87,0x34,0x8E,0x43,0x44,0xC4,0xDE,0xE9,0xCB,
0x54,0x7B,0x94,0x32,0xA6,0xC2,0x23,0x3D,0xEE,0x4C,0x95,0x0B,0x42,0xFA,0xC3,0x4E,
0x08,0x2E,0xA1,0x66,0x28,0xD9,0x24,0xB2,0x76,0x5B,0xA2,0x49,0x6D,0x8B,0xD1,0x25,
0x72,0xF8,0xF6,0x64,0x86,0x68,0x98,0x16,0xD4,0xA4,0x5C,0xCC,0x5D,0x65,0xB6,0x92,
0x6C,0x70,0x48,0x50,0xFD,0xED,0xB9,0xDA,0x5E,0x15,0x46,0x57,0xA7,0x8D,0x9D,0x84,
0x90,0xD8,0xAB,0x00,0x8C,0xBC,0xD3,0x0A,0xF7,0xE4,0x58,0x05,0xB8,0xB3,0x45,0x06,
0xD0,0x2C,0x1E,0x8F,0xCA,0x3F,0x0F,0x02,0xC1,0xAF,0xBD,0x03,0x01,0x13,0x8A,0x6B,
0x3A,0x91,0x11,0x41,0x4F,0x67,0xDC,0xEA,0x97,0xF2,0xCF,0xCE,0xF0,0xB4,0xE6,0x73,
0x96,0xAC,0x74,0x22,0xE7,0xAD,0x35,0x85,0xE2,0xF9,0x37,0xE8,0x1C,0x75,0xDF,0x6E,
0x47,0xF1,0x1A,0x71,0x1D,0x29,0xC5,0x89,0x6F,0xB7,0x62,0x0E,0xAA,0x18,0xBE,0x1B,
0xFC,0x56,0x3E,0x4B,0xC6,0xD2,0x79,0x20,0x9A,0xDB,0xC0,0xFE,0x78,0xCD,0x5A,0xF4,
0x1F,0xDD,0xA8,0x33,0x88,0x07,0xC7,0x31,0xB1,0x12,0x10,0x59,0x27,0x80,0xEC,0x5F,
0x60,0x51,0x7F,0xA9,0x19,0xB5,0x4A,0x0D,0x2D,0xE5,0x7A,0x9F,0x93,0xC9,0x9C,0xEF,
0xA0,0xE0,0x3B,0x4D,0xAE,0x2A,0xF5,0xB0,0xC8,0xEB,0xBB,0x3C,0x83,0x53,0x99,0x61,
0x17,0x2B,0x04,0x7E,0xBA,0x77,0xD6,0x26,0xE1,0x69,0x14,0x63,0x55,0x21,0x0C,0x7D
}
local rcon = {
0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,
0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,
0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,
0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,
0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,
0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,
0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,
0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,0x6a,0xd4,0xb3,
0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,0x25,0x4a,0x94,
0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,0x08,0x10,0x20,
0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,0x97,0x35,
0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,0x61,0xc2,0x9f,
0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d,0x01,0x02,0x04,
0x08,0x10,0x20,0x40,0x80,0x1b,0x36,0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,
0xc6,0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91,0x39,0x72,0xe4,0xd3,0xbd,
0x61,0xc2,0x9f,0x25,0x4a,0x94,0x33,0x66,0xcc,0x83,0x1d,0x3a,0x74,0xe8,0xcb,0x8d
}
local xor4 = {
[0]=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,
1,0,3,2,5,4,7,6,9,8,11,10,13,12,15,14,
2,3,0,1,6,7,4,5,10,11,8,9,14,15,12,13,
3,2,1,0,7,6,5,4,11,10,9,8,15,14,13,12,
4,5,6,7,0,1,2,3,12,13,14,15,8,9,10,11,
5,4,7,6,1,0,3,2,13,12,15,14,9,8,11,10,
6,7,4,5,2,3,0,1,14,15,12,13,10,11,8,9,
7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8,
8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7,
9,8,11,10,13,12,15,14,1,0,3,2,5,4,7,6,
10,11,8,9,14,15,12,13,2,3,0,1,6,7,4,5,
11,10,9,8,15,14,13,12,3,2,1,0,7,6,5,4,
12,13,14,15,8,9,10,11,4,5,6,7,0,1,2,3,
13,12,15,14,9,8,11,10,5,4,7,6,1,0,3,2,
14,15,12,13,10,11,8,9,6,7,4,5,2,3,0,1,
15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0,
}
local function xor8(a, b)
local al = a % 16
local bl = b % 16
return 16 * xor4[a - al + (b - bl) / 16] + xor4[16 * al + bl]
end
local function xor_blocks_8(a, b)
local res = {}
for i = 1, 16 do
res[i] = xor8(a[i], b[i])
end
return res
end
local function addRoundKey(state, key)
for i, byte in next, state do
state[i] = xor8(byte, key[i])
end
end
local function subBytes(state, s_box)
for i, byte in next, state do
state[i] = s_box[byte]
end
end
local function shiftRows(state)
state[2], state[6], state[10], state[14] =
state[6], state[10], state[14], state[2]
state[3], state[7], state[11], state[15] =
state[11], state[15], state[3], state[7]
state[4], state[8], state[12], state[16] =
state[16], state[4], state[8], state[12]
end
local function inv_shiftRows(state)
state[2], state[6], state[10], state[14] =
state[14], state[2], state[6], state[10]
state[3], state[7], state[11], state[15] =
state[11], state[15], state[3], state[7]
state[4], state[8], state[12], state[16] =
state[8], state[12], state[16], state[4]
end
local function mixColumns(state)
for i = 0, 3 do
local cur = i*4+1
local a, b, c, d = state[cur], state[cur + 1], state[cur + 2], state[cur + 3]
state[cur + 0] = xor8(xor8(xor8(GF8x2[a], GF8x3[b]), c), d)
state[cur + 1] = xor8(xor8(xor8(a, GF8x2[b]), GF8x3[c]), d)
state[cur + 2] = xor8(xor8(xor8(a, b), GF8x2[c]), GF8x3[d])
state[cur + 3] = xor8(xor8(xor8(GF8x3[a], b), c), GF8x2[d])
end
end
local function inv_mixColumns(state) -- TODO: fix
for i = 0, 3 do
local cur = i*4+1
local a, b, c, d = state[cur], state[cur + 1], state[cur + 2], state[cur + 3]
state[cur + 0] = xor8(xor8(xor8(GF8x14[a], GF8x11[b]), GF8x13[c]), GF8x9[d])
state[cur + 1] = xor8(xor8(xor8(GF8x9[a], GF8x14[b]), GF8x11[c]), GF8x13[d])
state[cur + 2] = xor8(xor8(xor8(GF8x13[a], GF8x9[b]), GF8x14[c]), GF8x11[d])
state[cur + 3] = xor8(xor8(xor8(GF8x11[a], GF8x13[b]), GF8x9[c]), GF8x14[d])
end
end
-- 256-bit key constants
local n = 32 -- number of bytes in the 256-bit encryption key
local b = 240 -- number of bytes in 15 128-bit round keys
local function schedule256(key)
local expanded = {}
for c = 0, n-1 do
expanded[c] = key[c]
end
local i = 1
local c = n
local t1, t2, t3, t4 --t
while c < b do
t1 = expanded[c-4]
t2 = expanded[c-3]
t3 = expanded[c-2]
t4 = expanded[c-1]
if (c % n == 0) then
t1, t2, t3, t4 = xor8(rcon[i+1], s[t2]), s[t3], s[t4], s[t1]
i = i + 1
end
if (c % n == 16) then
t1 = s[t1]
t2 = s[t2]
t3 = s[t3]
t4 = s[t4]
end
t1 = xor8(t1, expanded[c - n])
expanded[c] = t1
c = c + 1
t2 = xor8(t2, expanded[c - n])
expanded[c] = t2
c = c + 1
t3 = xor8(t3, expanded[c - n])
expanded[c] = t3
c = c + 1
t4 = xor8(t4, expanded[c - n])
expanded[c] = t4
c = c + 1
end
local roundKeys = {}
for round = 0, 14 do
local roundKey = {}
for byte = 0, 15 do
roundKey[byte+1] = expanded[round * 16 + byte]
end
roundKeys[round] = roundKey
end
return roundKeys
end
local function chunks(text, i)
local first = i * 16 + 1
if first > #text then
return
end
i = i + 1
local chunk = {text:byte(first, first + 15)}
for j = #chunk + 1, 16 do
chunk[j] = 0
end
return i, chunk
end
local function pkcs7_unpad(str)
local len = #str
if len == 0 then return str end
local pad_len = string.byte(str, len)
if pad_len < 1 or pad_len > 16 then
return nil
end
for i = len - pad_len + 1, len do
if string.byte(str, i) ~= pad_len then
return nil
end
end
return string.sub(str, 1, len - pad_len)
end
local function zero_unpad(str)
local len = #str
while len > 0 and string.byte(str, len) == 0 do
len = len - 1
end
return string.sub(str, 1, len)
end
local function unpad(str)
local unpadded = pkcs7_unpad(str)
if unpadded then
return unpadded
else
return zero_unpad(str)
end
end
local function encrypt(state, roundKeys)
addRoundKey(state, roundKeys[0])
for round = 1, 13 do
subBytes(state, s)
shiftRows(state)
mixColumns(state)
addRoundKey(state, roundKeys[round])
end
subBytes(state, s)
shiftRows(state)
addRoundKey(state, roundKeys[14])
end
local function decrypt(state, roundKeys)
addRoundKey(state, roundKeys[14])
inv_shiftRows(state)
subBytes(state, si)
for round = 13, 1, -1 do
addRoundKey(state, roundKeys[round])
inv_mixColumns(state)
inv_shiftRows(state)
subBytes(state, si)
end
addRoundKey(state, roundKeys[0])
end
local function ECB_encrypt(key, originaltext)
local text = {}
local roundKeys = schedule256(key)
local i = 0
while true do
i, state = chunks(originaltext, i)
if not state then break end
encrypt(state, roundKeys)
text[i] = string.char(unpack(state))
end
return table.concat(text)
end
local function ECB_decrypt(key, ciphertext)
local text = {}
local roundKeys = schedule256(key)
local i = 0
while true do
i, state = chunks(ciphertext, i)
if not state then break end
decrypt(state, roundKeys)
text[i] = string.char(unpack(state))
end
return unpad(table.concat(text))
end
local function CBC_encrypt(key, iv, originaltext)
local roundKeys = schedule256(key)
local text = {}
local prev_block = {unpack(iv)}
local i = 0
while true do
i, block = chunks(originaltext, i)
if not block then break end
local xored = xor_blocks_8(block, prev_block)
encrypt(xored, roundKeys)
text[i] = string.char(unpack(xored))
prev_block = xored
end
return table.concat(text)
end
local function CBC_decrypt(key, iv, ciphertext)
local roundKeys = schedule256(key)
local text = {}
local prev_block = {unpack(iv)}
local i = 0
while true do
i, block = chunks(ciphertext, i)
if not block then break end
local decrypted = {unpack(block)}
decrypt(decrypted, roundKeys)
local plain_block = xor_blocks_8(decrypted, prev_block)
text[i] = string.char(unpack(plain_block))
prev_block = block
end
local result = table.concat(text)
return unpad(result)
end
return {
ECB = {
encrypt = ECB_encrypt;
decrypt = ECB_decrypt;
};
CBC = {
encrypt = CBC_encrypt;
decrypt = CBC_decrypt;
};
}
+203
View File
@@ -0,0 +1,203 @@
--[[
base64 -- v1.5.3 public domain Lua base64 encoder/decoder
no warranty implied; use at your own risk
Needs bit32.extract function. If not present it's implemented using BitOp
or Lua 5.3 native bit operators. For Lua 5.1 fallbacks to pure Lua
implementation inspired by Rici Lake's post:
http://ricilake.blogspot.co.uk/2007/10/iterating-bits-in-lua.html
author: Ilya Kolbin (iskolbin@gmail.com)
url: github.com/iskolbin/lbase64
COMPATIBILITY
Lua 5.1+, LuaJIT
LICENSE
See end of file for license information.
--]]
local base64 = {}
local extract = _G.bit32 and _G.bit32.extract -- Lua 5.2/Lua 5.3 in compatibility mode
if not extract then
if _G.bit then -- LuaJIT
local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band
extract = function( v, from, width )
return band( shr( v, from ), shl( 1, width ) - 1 )
end
elseif _G._VERSION == "Lua 5.1" then
extract = function( v, from, width )
local w = 0
local flag = 2^from
for i = 0, width-1 do
local flag2 = flag + flag
if v % flag2 >= flag then
w = w + 2^i
end
flag = flag2
end
return w
end
else -- Lua 5.3+
extract = load[[return function( v, from, width )
return ( v >> from ) & ((1 << width) - 1)
end]]()
end
end
function base64.makeencoder( s62, s63, spad )
local encoder = {}
for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J',
'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y',
'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',
'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2',
'3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do
encoder[b64code] = char:byte()
end
return encoder
end
function base64.makedecoder( s62, s63, spad )
local decoder = {}
for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do
decoder[charcode] = b64code
end
return decoder
end
local DEFAULT_ENCODER = base64.makeencoder()
local DEFAULT_DECODER = base64.makedecoder()
local char, concat = string.char, table.concat
function base64.encode( str, encoder, usecaching )
encoder = encoder or DEFAULT_ENCODER
local t, k, n = {}, 1, #str
local lastn = n % 3
local cache = {}
for i = 1, n-lastn, 3 do
local a, b, c = str:byte( i, i+2 )
local v = a*0x10000 + b*0x100 + c
local s
if usecaching then
s = cache[v]
if not s then
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
cache[v] = s
end
else
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
end
t[k] = s
k = k + 1
end
if lastn == 2 then
local a, b = str:byte( n-1, n )
local v = a*0x10000 + b*0x100
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64])
elseif lastn == 1 then
local v = str:byte( n )*0x10000
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64])
end
return concat( t )
end
function base64.decode( b64, decoder, usecaching, schar1pos, schar2pos )
decoder = decoder or DEFAULT_DECODER
schar1pos = schar1pos or 62
schar2pos = schar2pos or 63
local pattern = '[^%w%+%/%=]'
if decoder then
local s62, s63
for charcode, b64code in pairs( decoder ) do
if b64code == schar1pos then s62 = charcode
elseif b64code == schar2pos then s63 = charcode
end
end
pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) )
end
b64 = b64:gsub( pattern, '' )
local cache = usecaching and {}
local t, k = {}, 1
local n = #b64
local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0
for i = 1, padding > 0 and n-4 or n, 4 do
local a, b, c, d = b64:byte( i, i+3 )
local s
if usecaching then
local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d
s = cache[v0]
if not s then
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
cache[v0] = s
end
else
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8))
end
t[k] = s
k = k + 1
end
if padding == 1 then
local a, b, c = b64:byte( n-3, n-1 )
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40
t[k] = char( extract(v,16,8), extract(v,8,8))
elseif padding == 2 then
local a, b = b64:byte( n-3, n-2 )
local v = decoder[a]*0x40000 + decoder[b]*0x1000
t[k] = char( extract(v,16,8))
end
return concat( t )
end
return base64
--[[
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2018 Ilya Kolbin
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
--]]
+159
View File
@@ -0,0 +1,159 @@
local unpack = unpack or table.unpack
-- Clean up media name
local function clean_name(name)
return name:gsub("^%[.-%]", " ")
:gsub("^%(.-%)", " ")
:gsub("[_%.%[%]]", " ")
:gsub("第%s*%d+%s*季", "")
:gsub("第%s*%d+%s*部", "")
:gsub("第[一二三四五六七八九十]+季", "")
:gsub("第[一二三四五六七八九十]+部", "")
:gsub("^%s*(.-)%s*$", "%1")
:gsub("[!@#%.%?%+%-%%&*_=,/~`]+$", "")
end
-- Formatters for media titles
local formatters = {
{
regex = "^(.-)%s*[_%-%.%s]%s*第%s*(%d+)%s*[季部]+%s*[_%-%.%s]%s*第%s*(%d+[%.v]?%d*)%s*[话集回]",
format = function(name, season, episode)
return clean_name(name) .. " S" .. season .. "E" .. episode:gsub("v%d+$","")
end
},
{
regex = "^(.-)%s*[_%-%.%s]%s*第([一二三四五六七八九十]+)[季部]+%s*[_%-%.%s]%s*第%s*(%d+[%.v]?%d*)%s*[话集回]",
format = function(name, season, episode)
return clean_name(name) .. " S" .. chinese_to_number(season) .. "E" .. episode:gsub("v%d+$","")
end
},
{
regex = "^(.-)%s*[_%-%.%s]%s*第%s*(%d+)%s*[季部]+%s*[_%-%.%s]%s*[^%ddD][eEpP]+(%d+[%.v]?%d*)",
format = function(name, season, episode)
return clean_name(name) .. " S" .. season .. "E" .. episode:gsub("v%d+$","")
end
},
{
regex = "^(.-)%s*[_%-%.%s]%s*第([一二三四五六七八九十]+)[季部]+%s*[_%-%.%s]%s*[^%ddD][eEpP]+(%d+[%.v]?%d*)",
format = function(name, season, episode)
return clean_name(name) .. " S" .. chinese_to_number(season) .. "E" .. episode:gsub("v%d+$","")
end
},
{
regex = "^(.-)%s*[_%.%s]%s*(%d%d%d%d)[_%.%s]%d%d[_%.%s]%d%d%s*[_%.%s]?(.-)%s*[_%.%s]%d+[pPkKxXbBfF]",
format = function(name, year, subtitle)
local title = clean_name(name)
if subtitle then
title = title .. ": " .. subtitle:gsub("%.", " "):gsub("^%s*(.-)%s*$", "%1")
end
return title .. " (" .. year .. ")"
end
},
{
regex = "^(.-)%s*[_%.%s]%s*(%d%d%d%d)%s*[_%.%s]%s*[sS](%d+)[%.%-%s:]?[eE](%d+%.?%d*)",
format = function(name, year, season, episode)
return clean_name(name) .. " (" .. year .. ") S" .. season .. "E" .. episode
end
},
{
regex = "^(.-)%s*[_%.%s]%s*(%d%d%d%d)%s*[_%.%s]%s*[^%ddD][eEpP]+(%d+%.?%d*)",
format = function(name, year, episode)
return clean_name(name) .. " (" .. year .. ") E" .. episode
end
},
{
regex = "^(.-)%s*[_%-%.%s]%s*[sS](%d+)[%.%-%s:]?[eE](%d+[%.v]?%d*)%s*[_%.%s]%s*(%d%d%d%d)[^%dhHxXvVpPkKxXbBfF]",
format = function(name, season, episode, year)
return clean_name(name) .. " (" .. year .. ") S" .. season .. "E" .. episode:gsub("v%d+$","")
end
},
{
regex = "^(.-)%s*[_%-%.%s]%s*[sS](%d+)[%.%-%s:]?[eE](%d+%.?%d*)",
format = function(name, season, episode)
return clean_name(name) .. " S" .. season .. "E" .. episode
end
},
{
regex = "^(.-)%s*[_%.%s]%s*(%d+)[nrdsth]+[_%.%s]%s*[sS]eason[_%.%s]%s*%[(%d+[%.v]?%d*)%]",
format = function(name, season, episode)
return clean_name(name) .. " S" .. season .. "E" .. episode:gsub("v%d+$","")
end
},
{
regex = "^(.-)%s*[^%ddD][eEpP]+(%d+[%.v]?%d*)[_%.%s]%s*(%d%d%d%d)[^%dhHxXvVpPkKxXbBfF]",
format = function(name, episode, year)
return clean_name(name) .. " (" .. year .. ") E" .. episode:gsub("v%d+$","")
end
},
{
regex = "^(.-)%s*[^%ddD][eEpP]+(%d+%.?%d*)",
format = function(name, episode)
return clean_name(name) .. " E" .. episode
end
},
{
regex = "^(.-)%s*第%s*(%d+[%.v]?%d*)%s*[话集回]",
format = function(name, episode)
return clean_name(name) .. " E" .. episode:gsub("v%d+$","")
end
},
{
regex = "^(.-)%s*%[(%d+[%.v]?%d*)%]",
format = function(name, episode)
return clean_name(name) .. " E" .. episode:gsub("v%d+$","")
end
},
{
regex = "^(.-)%s*%[(%d+[%.v]?%d*)%(%a+%)%]",
format = function(name, episode)
return clean_name(name) .. " E" .. episode:gsub("v%d+$","")
end
},
{
regex = "^(.-)%s*[%-#]%s*(%d+%.?%d*)%s*",
format = function(name, episode)
return clean_name(name) .. " E" .. episode
end
},
{
regex = "^(.-)%s*[%[%(]([OVADSPs]+)[%]%)]",
format = function(name, sp)
return clean_name(name) .. " [" .. sp .. "]"
end
},
{
regex = "^(.-)%s*[_%-%.%s]%s*(%d?%d)x(%d%d?%d?%d?)[^%dhHxXvVpPkKxXbBfF]",
format = function(name, season, episode)
return clean_name(name) .. " S" .. season .. "E" .. episode
end
},
{
regex = "^%((%d%d%d%d)%.?%d?%d?%.?%d?%d?%)%s*(.-)%s*[%(%[]",
format = function(year, name)
return clean_name(name) .. " (" .. year .. ")"
end
},
{
regex = "^(.-)%s*[_%.%s]%s*(%d%d%d%d)[^%dhHxXvVpPkKxXbBfF]",
format = function(name, year)
return clean_name(name) .. " (" .. year .. ")"
end
},
{
regex = "^%[.-%]%s*%[?(.-)%]?%s*[%(%[]",
format = function(name)
return clean_name(name)
end
},
}
-- Format filename based on regex patterns
function format_filename(title)
for _, formatter in ipairs(formatters) do
local matches = {title:match(formatter.regex)}
if #matches > 0 then
title = formatter.format(unpack(matches))
return title
end
end
end
+192
View File
@@ -0,0 +1,192 @@
--[[
sha256 -- public domain Lua SHA-256 implementation
no warranty implied; use at your own risk
author: dyphire
COMPATIBILITY
Lua 5.1+, LuaJIT
LICENSE: MIT License
--]]
local unpack = unpack or table.unpack
local function band(a,b)
local res = 0
local bit = 1
for i = 0,31 do
local aa = a % 2
local bb = b % 2
if aa == 1 and bb == 1 then
res = res + bit
end
a = (a - aa) / 2
b = (b - bb) / 2
bit = bit * 2
end
return res
end
local function bor(a,b)
local res = 0
local bit = 1
for i = 0,31 do
local aa = a % 2
local bb = b % 2
if aa == 1 or bb == 1 then
res = res + bit
end
a = (a - aa) / 2
b = (b - bb) / 2
bit = bit * 2
end
return res
end
local function bxor(a,b)
local res = 0
local bit = 1
for i = 0,31 do
local aa = a % 2
local bb = b % 2
if (aa + bb) == 1 then
res = res + bit
end
a = (a - aa) / 2
b = (b - bb) / 2
bit = bit * 2
end
return res
end
local function bnot(a)
return 0xFFFFFFFF - a
end
local function lshift(a,n)
return (a * 2^n) % 2^32
end
local function rshift(a,n)
return math.floor(a / 2^n) % 2^32
end
local function bit_ror(x, n)
return bor(rshift(x, n), lshift(x, 32 - n))
end
local function sha256(message)
local k = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
}
local function preprocess(msg)
local len = #msg
local bitLen = len * 8
msg = msg .. "\128"
local zeroPad = 64 - ((len + 9) % 64)
if zeroPad ~= 64 then
msg = msg .. string.rep("\0", zeroPad)
end
msg = msg .. string.char(
rshift(bitLen, 56) % 256,
rshift(bitLen, 48) % 256,
rshift(bitLen, 40) % 256,
rshift(bitLen, 32) % 256,
rshift(bitLen, 24) % 256,
rshift(bitLen, 16) % 256,
rshift(bitLen, 8) % 256,
bitLen % 256
)
return msg
end
local function chunkify(msg)
local chunks = {}
for i = 1, #msg, 64 do
table.insert(chunks, msg:sub(i, i + 63))
end
return chunks
end
local function processChunk(chunk, hash)
local w = {}
for i = 1, 64 do
if i <= 16 then
w[i] = lshift(string.byte(chunk, (i - 1) * 4 + 1), 24) +
lshift(string.byte(chunk, (i - 1) * 4 + 2), 16) +
lshift(string.byte(chunk, (i - 1) * 4 + 3), 8) +
string.byte(chunk, (i - 1) * 4 + 4)
else
local s0 = bxor(bxor(bit_ror(w[i - 15], 7), bit_ror(w[i - 15], 18)), rshift(w[i - 15], 3))
local s1 = bxor(bxor(bit_ror(w[i - 2], 17), bit_ror(w[i - 2], 19)), rshift(w[i - 2], 10))
w[i] = (w[i - 16] + s0 + w[i - 7] + s1) % 2^32
end
end
local a, b, c, d, e, f, g, h = unpack(hash)
for i = 1, 64 do
local s1 = bxor(bxor(bit_ror(e, 6), bit_ror(e, 11)), bit_ror(e, 25))
local ch = bxor(band(e, f), band(bnot(e), g))
local temp1 = (h + s1 + ch + k[i] + w[i]) % 2^32
local s0 = bxor(bxor(bit_ror(a, 2), bit_ror(a, 13)), bit_ror(a, 22))
local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
local temp2 = (s0 + maj) % 2^32
h = g
g = f
f = e
e = (d + temp1) % 2^32
d = c
c = b
b = a
a = (temp1 + temp2) % 2^32
end
return
(hash[1] + a) % 2^32,
(hash[2] + b) % 2^32,
(hash[3] + c) % 2^32,
(hash[4] + d) % 2^32,
(hash[5] + e) % 2^32,
(hash[6] + f) % 2^32,
(hash[7] + g) % 2^32,
(hash[8] + h) % 2^32
end
message = preprocess(message)
local chunks = chunkify(message)
local hash = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
}
for _, chunk in ipairs(chunks) do
hash = {processChunk(chunk, hash)}
end
local result = ""
for _, h in ipairs(hash) do
result = result .. string.format("%08x", h)
end
return result
end
return sha256
+164
View File
@@ -0,0 +1,164 @@
-- taken from https://github.com/rkscv/danmaku/blob/main/danmaku.lua
-- modified from https://bitop.luajit.org/download.html (LuaBitOp-1.0.2 / md5test.lua)
-- and https://github.com/kikito/md5.lua/blob/master/md5.lua
-- SPDX-License-Identifier:MIT
local byte, char, sub, rep = string.byte, string.char, string.sub, string.rep
local tobit, tohex, bnot, bor, band, bxor, lshift, rshift, rol, bswap
if _G.bit then --LuaJIT
tobit, tohex = _G.bit.tobit or _G.bit.cast, _G.bit.tohex
bnot, bor, band, bxor, lshift, rshift = _G.bit.bnot, _G.bit.bor, _G.bit.band, _G.bit.bxor, _G.bit.lshift, _G.bit.rshift
rol, bswap = _G.bit.rol, _G.bit.bswap
elseif _G.bit32 then --Lua 5.2
local bit32_bnot = _G.bit32.bnot
tobit = function(a) return a <= 0x7fffffff and a or -(_G.bit32.bnot(a) + 1) end
bnot = function(a) return tobit(bit32_bnot(tobit(a))) end
bor, band, bxor, lshift, rshift, rol = _G.bit32.bor, _G.bit32.band, _G.bit32.bxor, _G.bit32.lshift, _G.bit32.rshift, _G.bit32.lrotate
else
return nil
end
if not tohex then
tohex = function(a) return string.sub(string.format('%08x', a), -8) end
end
if not bswap then
bswap = function(a)
return bor(rshift(a, 24), band(rshift(a, 8), 0xff00), lshift(band(a, 0xff00), 8), lshift(a, 24))
end
end
local function tr_f(a, b, c, d, x, s) return rol(bxor(d, band(b, bxor(c, d))) + a + x, s) + b end
local function tr_g(a, b, c, d, x, s) return rol(bxor(c, band(d, bxor(b, c))) + a + x, s) + b end
local function tr_h(a, b, c, d, x, s) return rol(bxor(b, c, d) + a + x, s) + b end
local function tr_i(a, b, c, d, x, s) return rol(bxor(c, bor(b, bnot(d))) + a + x, s) + b end
local function transform(x, a1, b1, c1, d1)
local a, b, c, d = a1, b1, c1, d1
a = tr_f(a, b, c, d, x[1] + 0xd76aa478, 7)
d = tr_f(d, a, b, c, x[2] + 0xe8c7b756, 12)
c = tr_f(c, d, a, b, x[3] + 0x242070db, 17)
b = tr_f(b, c, d, a, x[4] + 0xc1bdceee, 22)
a = tr_f(a, b, c, d, x[5] + 0xf57c0faf, 7)
d = tr_f(d, a, b, c, x[6] + 0x4787c62a, 12)
c = tr_f(c, d, a, b, x[7] + 0xa8304613, 17)
b = tr_f(b, c, d, a, x[8] + 0xfd469501, 22)
a = tr_f(a, b, c, d, x[9] + 0x698098d8, 7)
d = tr_f(d, a, b, c, x[10] + 0x8b44f7af, 12)
c = tr_f(c, d, a, b, x[11] + 0xffff5bb1, 17)
b = tr_f(b, c, d, a, x[12] + 0x895cd7be, 22)
a = tr_f(a, b, c, d, x[13] + 0x6b901122, 7)
d = tr_f(d, a, b, c, x[14] + 0xfd987193, 12)
c = tr_f(c, d, a, b, x[15] + 0xa679438e, 17)
b = tr_f(b, c, d, a, x[16] + 0x49b40821, 22)
a = tr_g(a, b, c, d, x[2] + 0xf61e2562, 5)
d = tr_g(d, a, b, c, x[7] + 0xc040b340, 9)
c = tr_g(c, d, a, b, x[12] + 0x265e5a51, 14)
b = tr_g(b, c, d, a, x[1] + 0xe9b6c7aa, 20)
a = tr_g(a, b, c, d, x[6] + 0xd62f105d, 5)
d = tr_g(d, a, b, c, x[11] + 0x02441453, 9)
c = tr_g(c, d, a, b, x[16] + 0xd8a1e681, 14)
b = tr_g(b, c, d, a, x[5] + 0xe7d3fbc8, 20)
a = tr_g(a, b, c, d, x[10] + 0x21e1cde6, 5)
d = tr_g(d, a, b, c, x[15] + 0xc33707d6, 9)
c = tr_g(c, d, a, b, x[4] + 0xf4d50d87, 14)
b = tr_g(b, c, d, a, x[9] + 0x455a14ed, 20)
a = tr_g(a, b, c, d, x[14] + 0xa9e3e905, 5)
d = tr_g(d, a, b, c, x[3] + 0xfcefa3f8, 9)
c = tr_g(c, d, a, b, x[8] + 0x676f02d9, 14)
b = tr_g(b, c, d, a, x[13] + 0x8d2a4c8a, 20)
a = tr_h(a, b, c, d, x[6] + 0xfffa3942, 4)
d = tr_h(d, a, b, c, x[9] + 0x8771f681, 11)
c = tr_h(c, d, a, b, x[12] + 0x6d9d6122, 16)
b = tr_h(b, c, d, a, x[15] + 0xfde5380c, 23)
a = tr_h(a, b, c, d, x[2] + 0xa4beea44, 4)
d = tr_h(d, a, b, c, x[5] + 0x4bdecfa9, 11)
c = tr_h(c, d, a, b, x[8] + 0xf6bb4b60, 16)
b = tr_h(b, c, d, a, x[11] + 0xbebfbc70, 23)
a = tr_h(a, b, c, d, x[14] + 0x289b7ec6, 4)
d = tr_h(d, a, b, c, x[1] + 0xeaa127fa, 11)
c = tr_h(c, d, a, b, x[4] + 0xd4ef3085, 16)
b = tr_h(b, c, d, a, x[7] + 0x04881d05, 23)
a = tr_h(a, b, c, d, x[10] + 0xd9d4d039, 4)
d = tr_h(d, a, b, c, x[13] + 0xe6db99e5, 11)
c = tr_h(c, d, a, b, x[16] + 0x1fa27cf8, 16)
b = tr_h(b, c, d, a, x[3] + 0xc4ac5665, 23)
a = tr_i(a, b, c, d, x[1] + 0xf4292244, 6)
d = tr_i(d, a, b, c, x[8] + 0x432aff97, 10)
c = tr_i(c, d, a, b, x[15] + 0xab9423a7, 15)
b = tr_i(b, c, d, a, x[6] + 0xfc93a039, 21)
a = tr_i(a, b, c, d, x[13] + 0x655b59c3, 6)
d = tr_i(d, a, b, c, x[4] + 0x8f0ccc92, 10)
c = tr_i(c, d, a, b, x[11] + 0xffeff47d, 15)
b = tr_i(b, c, d, a, x[2] + 0x85845dd1, 21)
a = tr_i(a, b, c, d, x[9] + 0x6fa87e4f, 6)
d = tr_i(d, a, b, c, x[16] + 0xfe2ce6e0, 10)
c = tr_i(c, d, a, b, x[7] + 0xa3014314, 15)
b = tr_i(b, c, d, a, x[14] + 0x4e0811a1, 21)
a = tr_i(a, b, c, d, x[5] + 0xf7537e82, 6)
d = tr_i(d, a, b, c, x[12] + 0xbd3af235, 10)
c = tr_i(c, d, a, b, x[3] + 0x2ad7d2bb, 15)
b = tr_i(b, c, d, a, x[10] + 0xeb86d391, 21)
return tobit(a + a1), tobit(b + b1), tobit(c + c1), tobit(d + d1)
end
local function md5_update(self, s)
local m, len = s, #s
if len % 4 ~= 0 then
m = m .. '\128' .. rep('\0', 63 - band(len + 8, 63)) ..
char(band(lshift(len, 3), 255), band(rshift(len, 5), 255), band(rshift(len, 13), 255),
band(rshift(len, 21), 255)) .. '\0\0\0\0'
end
local a, b, c, d = self.a, self.b, self.c, self.d
local x, k = self.x, self.k
for i = 1, #m, 4 do
local m0, m1, m2, m3 = byte(m, i, i + 3)
x[k] = bor(m0, lshift(m1, 8), lshift(m2, 16), lshift(m3, 24))
if k == 16 then
a, b, c, d = transform(x, a, b, c, d)
k = 1
else
k = k + 1
end
end
self.a, self.b, self.c, self.d, self.k = a, b, c, d, k
self.len = self.len + len
return self
end
local function md5_finish(self)
local len = self.len
if len % 4 == 0 then
local s = '\128' .. rep('\0', 63 - band(len + 8, 63)) ..
char(band(lshift(len, 3), 255), band(rshift(len, 5), 255), band(rshift(len, 13), 255),
band(rshift(len, 21), 255)) .. '\0\0\0\0'
md5_update(self, s)
end
return tohex(bswap(self.a)) .. tohex(bswap(self.b)) .. tohex(bswap(self.c)) .. tohex(bswap(self.d))
end
local md5 = {}
function md5.new()
return {
a = 0x67452301,
b = 0xefcdab89,
c = 0x98badcfe,
d = 0x10325476,
x = {},
k = 1,
len = 0,
update = md5_update,
finish = md5_finish,
}
end
function md5.sum(s)
return md5.new():update(s):finish()
end
return md5
File diff suppressed because it is too large Load Diff
+77
View File
@@ -0,0 +1,77 @@
local opt = require("mp.options")
-- 选项
options = {
-- 指定弹幕服务器地址,自定义服务需兼容 dandanplay 的 api
api_server = "https://api.dandanplay.net",
-- 指定 b 站和爱腾优的弹幕获取的兜底服务器地址,主要用于获取非动画弹幕
-- 可用: https://api.danmu.icuhttps://dmku.hls.one
fallback_server = "https://api.danmu.icu",
-- 设置 tmdb 的 API Key用于获取非动画条目的中文信息(当搜索内容非中文时)
-- 可以在 https://www.themoviedb.org 注册后去个人账号设置界面获取
-- 注意:自定义此参数时还需要对获取到的 API Key 进行 base64 编码
tmdb_api_key = "NmJmYjIxOTZkNzIyN2UyMTIzMGM3Y2YzZjQ4MDNkZGM=",
auto_load = false,
autoload_local_danmaku = false,
autoload_for_url = false,
save_danmaku = false,
user_agent = "mpv_danmaku/1.0",
proxy = "",
-- 可选:向 HTTP 请求传递 cookie.txt 文件路径
cookie_file = "",
-- 使用 fps 视频滤镜,大幅提升弹幕平滑度。默认禁用
vf_fps = false,
-- 设置要使用的 fps 滤镜参数
fps = "60/1.001",
-- 指定合并重复弹幕的时间间隔的容差值,单位为秒。默认值: -1表示禁用
merge_tolerance = -1,
-- 指定弹幕关联历史记录文件的路径,支持绝对路径和相对路径
history_path = "~~/danmaku-history.json",
open_search_danmaku_menu_key = "Ctrl+d",
show_danmaku_keyboard_key = "j",
-- 中文简繁转换。0-不转换1-转换为简体2-转换为繁体
chConvert = 0,
--滚动弹幕的显示时间
scrolltime = 15,
--固定弹幕的显示时间
fixtime = 5,
--字体
fontname = "sans-serif",
--字体大小
fontsize = 50,
--字体阴影
shadow = 0,
--字体粗体
bold = true,
-- 透明度0完全透明到 1不透明
opacity = 0.7,
--全部弹幕的显示范围(0.0-1.0)
displayarea = 0.85,
--描边 0-4
outline = 1.0,
-- 限制屏幕中同时显示的最大弹幕数量0 表示不限制
max_screen_danmaku = 0,
--指定弹幕屏蔽词文件路径(black.txt),支持绝对路径和相对路径。文件内容以换行分隔
--支持 lua 的正则表达式写法
blacklist_path = "",
--指定脚本相关消息显示的消息的对齐方式
message_anlignment = 7,
--指定脚本相关消息显示的消息的x轴坐标
message_x = 30,
--指定脚本相关消息显示的消息的y轴坐标
message_y = 30,
-- 自定义标题解析中的额外替换规则,内容格式为 JSON 字符串,替换模式为 lua 的 string.gsub 函数
--! 注意:由于 mpv 的 lua 版本限制,自定义规则只支持形如 %n 的捕获组写法,即示例用法,不支持直接替换字符的写法
title_replace = [[
[{
"rules": [{ "^(.-)": "%1"},{ "^.*《(.-)》": "%1" }],
}]
]],
-- 指定哈希匹配中需忽略的共享盘(挂载盘)的路径/目录。支持绝对路径和相对路径,多个路径用逗号分隔
-- 示例:["X:", "Z:", "F:/Download/", "Download"]
excluded_path = [[
[]
]],
}
opt.read_options(options, mp.get_script_name(), function() end)
+720
View File
@@ -0,0 +1,720 @@
local msg = require 'mp.msg'
local utils = require 'mp.utils'
local s2t = require("dicts/s2t_chars")
local t2s = require("dicts/t2s_chars")
local function ass_escape(text)
return text:gsub("\\", "\\\\")
:gsub("{", "\\{")
:gsub("}", "\\}")
:gsub("\n", "\\N")
end
local function xml_unescape(str)
return str:gsub("&quot;", "\"")
:gsub("&apos;", "'")
:gsub("&gt;", ">")
:gsub("&lt;", "<")
:gsub("&amp;", "&")
end
local function decode_html_entities(text)
return text:gsub("&#x([%x]+);", function(hex)
local codepoint = tonumber(hex, 16)
return unicode_to_utf8(codepoint)
end):gsub("&#(%d+);", function(dec)
local codepoint = tonumber(dec, 10)
return unicode_to_utf8(codepoint)
end)
end
-- 加载黑名单模式
local function load_blacklist_patterns(filepath)
local patterns = {}
if not file_exists(filepath) then
return patterns
end
local file = io.open(filepath, "r")
if not file then
msg.error("无法打开黑名单文件: " .. filepath)
return patterns
end
if string.match(filepath, "%.xml$") then
-- xml文件格式示例
--<?xml version="1.0" encoding="utf-8"?>
--<filters>
-- <item enabled="true">t=卡在</item>
-- <item enabled="true">t=进度条</item>
--</filters>
print("加载黑名单文件: " .. filepath)
for line in file:lines() do
local pattern = line:match('<item%s+enabled="true">t=(.-)</item>')
if pattern then
print("加载黑名单模式: " .. pattern)
table.insert(patterns, pattern)
end
end
end
if string.match(filepath, "%.json$") then
-- json文件格式示例
-- [{"type":0,"filter":"开门","opened":true,"id":15628936}
-- ,{"type":0,"filter":"tony","opened":true,"id":15628939}
-- ,{"type":1,"filter":"0+.1","opened":true,"id":15628951}]
local content = read_file(filepath)
if content then
local json = utils.parse_json(content)
if json and type(json) == "table" then
for _, entry in ipairs(json) do
if entry.opened and entry.filter and entry.type == 0 then
table.insert(patterns, entry.filter)
end
end
end
end
end
if string.match(filepath, "%.txt$") then
-- 文本文件格式示例
-- 卡在
-- 进度条
for line in file:lines() do
line = line:match("^%s*(.-)%s*$")
if line ~= "" then
table.insert(patterns, line)
end
end
end
file:close()
return patterns
end
local blacklist_file = mp.command_native({ "expand-path", options.blacklist_path })
local black_patterns = load_blacklist_patterns(blacklist_file)
-- 检查字符串是否在黑名单中
function is_blacklisted(str, patterns)
for _, pattern in ipairs(patterns) do
local ok, result = pcall(function()
return str:match(pattern)
end)
if ok and result then
return true, pattern
elseif not ok then
-- msg.debug("黑名单规则错误,跳过: " .. pattern .. ",错误信息:" .. result)
end
end
return false
end
-- 简繁转换
local function convert(text, dict)
return text:gsub("[%z\1-\127\194-\244][\128-\191]*", function(c)
return dict[c] or c
end)
end
local function ch_convert(str)
if options.chConvert == 1 then
return convert(str, t2s)
elseif options.chConvert == 2 then
return convert(str, s2t)
end
return str
end
local ch_convert_cache = {}
local ch_cache_keys = {}
local ch_cache_max = 5000
local function ch_convert_cached(text)
if type(text) ~= "string" or text == "" then return text end
local cached = ch_convert_cache[text]
if cached ~= nil then return cached end
local converted = ch_convert(text)
ch_convert_cache[text] = converted
ch_cache_keys[#ch_cache_keys+1] = text
if #ch_cache_keys > ch_cache_max then
local old_key = table.remove(ch_cache_keys, 1)
ch_convert_cache[old_key] = nil
end
return converted
end
-- 合并重复弹幕
local function merge_duplicate_danmaku(danmakus, threshold)
if not threshold or tonumber(threshold) < 0 then return danmakus end
local groups = {}
for _, d in ipairs(danmakus) do
local tkey = tostring(d.type or "")
local ckey = tostring(d.color or "")
local text = d.text or ""
groups[tkey] = groups[tkey] or {}
groups[tkey][ckey] = groups[tkey][ckey] or {}
groups[tkey][ckey][text] = groups[tkey][ckey][text] or {}
table.insert(groups[tkey][ckey][text], d)
end
local merged = {}
local abs = math.abs
for _, bytype in pairs(groups) do
for _, bycolor in pairs(bytype) do
for _, group in pairs(bycolor) do
table.sort(group, function(a, b) return a.time < b.time end)
local i = 1
while i <= #group do
local base = group[i]
local times = { base.time }
local count = 1
local j = i + 1
while j <= #group and abs(group[j].time - base.time) <= threshold do
times[#times+1] = group[j].time
count = count + 1
j = j + 1
end
local same_time = true
for k = 2, #times do
if times[k] ~= times[1] then
same_time = false
break
end
end
local danmaku = {
time = base.time,
type = base.type,
size = base.size,
color = base.color,
text = base.text,
source = base.source,
orig_time = base.orig_time,
}
if count > 2 or not same_time then
danmaku.text = danmaku.text .. string.format("x%d", count)
end
table.insert(merged, danmaku)
i = j
end
end
end
end
table.sort(merged, function(a, b) return a.time < b.time end)
return merged
end
-- 限制每屏弹幕条数
local function limit_danmaku(danmakus, limit)
if not limit or limit <= 0 then
return danmakus
end
local window = {}
for _, d in ipairs(danmakus) do
for i = #window, 1, -1 do
if window[i].end_time <= d.start_time then
table.remove(window, i)
end
end
if #window < limit then
table.insert(window, d)
else
local max_idx = 1
for i = 2, #window do
if window[i].end_time > window[max_idx].end_time then
max_idx = i
end
end
if window[max_idx].end_time > d.end_time then
window[max_idx].drop = true
window[max_idx] = d
else
d.drop = true
end
end
end
local result = {}
for _, d in ipairs(danmakus) do
if not d.drop then
table.insert(result, d)
end
end
return result
end
-- 解析 XML 弹幕
local function parse_xml_danmaku(xml_string)
local danmakus = {}
-- [^>]* 匹配其他 attributes
-- %f[^%s] 确保 p= 前面是空白字符
for p_attr, text in xml_string:gmatch('<d%s+[^>]*%f[^%s]p="([^"]+)"[^>]*>([^<]+)</d>') do
local params = {}
local i = 1
for val in p_attr:gmatch("([^,]+)") do
params[i] = tonumber(val)
i = i + 1
end
if params[1] and params[2] and params[3] and params[4] then
table.insert(danmakus, {
time = params[1],
type = params[2] or 1,
size = params[3] or 25,
color = params[4] or 0xFFFFFF,
text = xml_unescape(text)
})
end
end
table.sort(danmakus, function(a, b) return a.time < b.time end)
return danmakus
end
-- 解析 JSON 弹幕
local function parse_json_danmaku(json_string)
local danmakus = {}
if json_string:sub(1, 3) == "\239\187\191" then
json_string = json_string:sub(4)
end
local json = utils.parse_json(json_string)
if not json or type(json) ~= "table" then
msg.info("JSON 解析失败")
return danmakus
end
for _, entry in ipairs(json) do
local c = entry.c
local text = entry.m or ""
if type(c) == "string" then
local params = {}
local i = 1
for val in c:gmatch("([^,]+)") do
params[i] = tonumber(val)
i = i + 1
end
if params[1] and params[2] and params[3] and params[4] then
table.insert(danmakus, {
time = params[1],
color = params[2] or 0xFFFFFF,
type = params[3] or 1,
size = params[4] or 25,
text = text
})
end
end
end
table.sort(danmakus, function(a, b) return a.time < b.time end)
return danmakus
end
-- 解析弹幕文件
function parse_danmaku_file(danmaku_input)
local danmakus = {}
if file_exists(danmaku_input) then
local content = read_file(danmaku_input)
if content then
local parsed = {}
if danmaku_input:match("%.xml$") then
parsed = parse_xml_danmaku(content)
elseif danmaku_input:match("%.json$") then
parsed = parse_json_danmaku(content)
end
for _, d in ipairs(parsed) do
table.insert(danmakus, d)
end
else
msg.info("无法读取文件内容: " .. danmaku_input)
end
else
msg.info("文件不存在: " .. danmaku_input)
end
for _, d in ipairs(danmakus) do
if d.orig_time == nil then d.orig_time = d.time end
end
if #danmakus == 0 then
msg.info("未能解析任何弹幕")
return nil
end
return danmakus
end
--# 弹幕数组与布局算法 (Danmaku Array & Layout Algorithms)
local DanmakuArray = {}
DanmakuArray.__index = DanmakuArray
function DanmakuArray:new(res_x, res_y, font_size)
local obj = {
solution_y = res_y,
font_size = font_size,
rows = math.floor(res_y / font_size),
time_length_array = {}
}
for i = 1, obj.rows do
obj.time_length_array[i] = { time = 0, length = 0, empty = true }
end
setmetatable(obj, self)
return obj
end
function DanmakuArray:set_time_length(row, time, length)
if row > 0 and row <= self.rows then
self.time_length_array[row] = { time = time, length = length, empty = false }
end
end
function DanmakuArray:get_time(row)
if row > 0 and row <= self.rows then
return self.time_length_array[row].time
end
return -1
end
function DanmakuArray:get_length(row)
if row > 0 and row <= self.rows then
return self.time_length_array[row].length
end
return 0
end
function DanmakuArray:is_empty(row)
if row > 0 and row <= self.rows then
return self.time_length_array[row].empty
end
return false
end
-- 滚动弹幕 Y 坐标算法
function get_position_y(font_size, appear_time, text_length, resolution_x, roll_time, array)
local velocity = (text_length + resolution_x) / roll_time
for i = 1, array.rows do
local previous_appear_time = array:get_time(i)
if array:is_empty(i) then
array:set_time_length(i, appear_time, text_length)
return 1 + (i - 1) * font_size
end
local previous_length = array:get_length(i)
local previous_velocity = (previous_length + resolution_x) / roll_time
local delta_velocity = velocity - previous_velocity
local delta_x = (appear_time - previous_appear_time) * previous_velocity - previous_length
if delta_x >= 0 then
if delta_velocity <= 0 then
array:set_time_length(i, appear_time, text_length)
return 1 + (i - 1) * font_size
end
local delta_time = delta_x / delta_velocity
if delta_time >= roll_time then
array:set_time_length(i, appear_time, text_length)
return 1 + (i - 1) * font_size
end
end
end
-- 所有行都被占用,放弃渲染
return nil
end
-- 固定弹幕 Y 坐标算法
function get_fixed_y(font_size, appear_time, fixtime, array, from_top)
local row_start, row_end, row_step
if from_top then
row_start, row_end, row_step = 1, array.rows, 1
else
row_start, row_end, row_step = array.rows, 1, -1
end
for i = row_start, row_end, row_step do
local previous_appear_time = array:get_time(i)
if array:is_empty(i) then
array:set_time_length(i, appear_time, 0)
return (i - 1) * font_size + 1
else
local delta_time = appear_time - previous_appear_time
if delta_time > fixtime then
array:set_time_length(i, appear_time, 0)
return (i - 1) * font_size + 1
end
end
end
-- 所有行都被占用,放弃渲染
return nil
end
-- 将弹幕转换为 XML 格式
function convert_danmaku_to_xml(danmaku_out)
local danmakus = {}
for _, source in pairs(DANMAKU.sources) do
if not source.blocked and source.data then
for _, d in ipairs(source.data) do
table.insert(danmakus, d)
end
end
end
if #danmakus == 0 then
show_message("弹幕内容为空,无法保存", 3)
msg.verbose("弹幕内容为空,无法保存")
COMMENTS = {}
return false
end
-- 拼接为 XML 内容
local xml = { '<?xml version="1.0" encoding="UTF-8"?><i>\n' }
for _, d in ipairs(danmakus) do
local time = d.time
local type = d.type or 1
local size = d.size or 25
local color = d.color or 0xFFFFFF
local text = d.text or ""
text = text:gsub("&", "&amp;")
:gsub("<", "&lt;")
:gsub(">", "&gt;")
:gsub("\"", "&quot;")
:gsub("'", "&apos;")
table.insert(xml, string.format('<d p="%s,%s,%s,%s">%s</d>\n', time, type, size, color, text))
end
table.insert(xml, '</i>')
-- 写入 XML 文件
local file = io.open(danmaku_out, "w")
if not file then
show_message("无法写入目标 XML 文件", 3)
msg.info("无法写入目标 XML 文件: " .. danmaku_out)
return false
end
file:write(table.concat(xml))
file:close()
show_message("转换 XML 弹幕成功: " .. danmaku_out, 3)
msg.info("转换 XML 弹幕成功: " .. danmaku_out)
return true
end
function convert_danmaku_to_ass_events(force)
local per_source_lists = {}
for url, source in pairs(DANMAKU.sources) do
if not source.blocked and source.data then
local segments = nil
local prefix = nil
if source.delay_segments and #source.delay_segments > 0 then
segments = {}
for i, v in ipairs(source.delay_segments) do segments[i] = v end
table.sort(segments, function(a, b) return (a.start or 0) < (b.start or 0) end)
prefix = {}
local s = 0
for i, v in ipairs(segments) do
s = s + (v.delay or 0)
prefix[i] = s
end
end
local function get_cached_delay(t)
local segs = segments or {}
local pre = prefix or {}
if #segs == 0 then return 0 end
local idx = binary_search(segs, t, function(s) return (s and s.start) or 0 end)
local target = idx - 1
if target < 1 then return 0 end
return pre[target] or 0
end
local list = {}
for _, d in ipairs(source.data) do
local base_time = d.orig_time or d.time
if d.orig_time == nil then d.orig_time = base_time end
local adjusted_time = base_time + get_cached_delay(base_time)
local entry = {
orig_time = d.orig_time,
time = adjusted_time,
type = d.type,
size = d.size,
color = d.color,
text = d.text,
source = url,
}
if not is_blacklisted(d.text, black_patterns) then
table.insert(list, entry)
end
end
if #list > 0 then
table.sort(list, function(a, b) return a.time < b.time end)
per_source_lists[#per_source_lists + 1] = list
end
end
end
local danmakus = {}
local heap = new_min_heap()
for li = 1, #per_source_lists do
local lst = per_source_lists[li]
if lst and #lst > 0 then
heap.push({ time = lst[1].time, list_idx = li, pos = 1, entry = lst[1] })
end
end
while true do
local node = heap.pop()
if not node then break end
table.insert(danmakus, node.entry)
local li = node.list_idx
local next_pos = node.pos + 1
local lst = per_source_lists[li]
if lst and lst[next_pos] then
heap.push({ time = lst[next_pos].time, list_idx = li, pos = next_pos, entry = lst[next_pos] })
end
end
if options.max_screen_danmaku > 0 and options.merge_tolerance <= 0 then
options.merge_tolerance = options.scrolltime
end
danmakus = merge_duplicate_danmaku(danmakus, options.merge_tolerance)
if #danmakus == 0 then
if not force then
show_message("该集弹幕内容为空,结束加载", 3)
msg.verbose("该集弹幕内容为空,结束加载")
end
COMMENTS = {}
return
end
if not force then
msg.info("已解析 " .. #danmakus .. " 条弹幕")
end
local fontsize = tonumber(options.fontsize) or 50
local scrolltime = tonumber(options.scrolltime) or 15
local fixtime = tonumber(options.fixtime) or 5
local res_x = 1920
local res_y = 1080
local roll_array = DanmakuArray:new(res_x, res_y, fontsize)
local top_array = DanmakuArray:new(res_x, res_y, fontsize)
-- 预处理弹幕,先计算时间段以便进行数量限制
local pre_events = {}
for _, d in ipairs(danmakus) do
local time = d.type == 1 and math.floor(d.time + 0.5) or d.time
local orig_time = d.type == 1 and math.floor(d.orig_time + 0.5) or d.orig_time
local appear_time = time
local danmaku_type = d.type
local end_time = nil
if danmaku_type >= 1 and danmaku_type <= 3 then
end_time = appear_time + scrolltime
elseif danmaku_type == 5 or danmaku_type == 4 then
end_time = appear_time + fixtime
end
if end_time then
table.insert(pre_events, {orig_time = orig_time, start_time = appear_time, end_time = end_time, danmaku = d})
end
end
if options.max_screen_danmaku > 0 then
pre_events = limit_danmaku(pre_events, options.max_screen_danmaku)
end
local ass_events = {}
for _, ev in ipairs(pre_events) do
local d = ev.danmaku
local appear_time = ev.start_time
local danmaku_type = d.type
local clean_text = ch_convert_cached(decode_html_entities(d.text))
local text = ass_escape(clean_text)
:gsub("x(%d+)$", "{\\b1\\i1}x%1")
-- 颜色从十进制转为 BGR Hex
local color = math.max(0, math.min(d.color or 0xFFFFFF, 0xFFFFFF))
local color_hex = string.format("%06X", color)
local r = string.sub(color_hex, 1, 2)
local g = string.sub(color_hex, 3, 4)
local b = string.sub(color_hex, 5, 6)
local color_text = string.format("{\\c&H%s%s%s&}", b, g, r)
local style, effect
local pos, move = nil, nil
-- 滚动弹幕 (类型 1, 2, 3)
if danmaku_type >= 1 and danmaku_type <= 3 then
style = "R2L"
local text_length = get_str_width(text, fontsize)
local x1 = res_x + text_length / 2
local x2 = -text_length / 2
local y = get_position_y(fontsize, appear_time, text_length, res_x, scrolltime, roll_array)
if y then
effect = string.format("{\\move(%d, %d, %d, %d)}", x1, y, x2, y)
move = {x1, y, x2, y}
end
-- 顶部弹幕 (类型 5)
elseif danmaku_type == 5 then
style = "TOP"
local x = res_x / 2
local y = get_fixed_y(fontsize, appear_time, fixtime, top_array, true)
if y then
effect = string.format("{\\pos(%d, %d)}", x, y)
pos = {x, y}
end
-- 底部弹幕 (类型 4)
elseif danmaku_type == 4 then
style = "BTM"
local x = res_x / 2
local y = get_fixed_y(fontsize, appear_time, fixtime, top_array, false)
if y then
effect = string.format("{\\pos(%d, %d)}", x, y)
pos = {x, y}
end
end
if style and effect then
text = effect .. color_text .. text
local event = {
orig_time = ev.orig_time,
start_time = ev.start_time,
end_time = ev.end_time,
delay = ev.start_time - (ev.orig_time or ev.start_time),
style = style,
text = text,
clean_text = clean_text,
pos = pos,
move = move,
source = d.source,
}
table.insert(ass_events, event)
COMMENTS = ass_events
end
end
end
+259
View File
@@ -0,0 +1,259 @@
-- modified from https://github.com/rkscv/danmaku/blob/main/danmaku.lua
local msg = require('mp.msg')
local utils = require("mp.utils")
local unpack = unpack or table.unpack
local osd_width, osd_height, pause = 0, 0, true
local time_pos_observer_active = false
local overlay = mp.create_osd_overlay('ass-events')
local function realtime_position_text(event, pos, displayarea)
if not event.move then
local _, current_y = unpack(event.pos)
if not current_y or tonumber(current_y) > displayarea then return end
if event.style ~= "SP" and event.style ~= "MSG" then
return string.format("{\\an8}%s", event.text)
else
return string.format("{\\an7}%s", event.text)
end
end
local x1, y1, x2, y2 = unpack(event.move)
-- 计算移动的时间范围
local duration = event.end_time - event.start_time --mean: options.scrolltime
local progress = (pos - event.start_time) / duration -- 移动进度 [0, 1]
-- 计算当前坐标
local current_x = tonumber(x1 + (x2 - x1) * progress)
local current_y = tonumber(y1 + (y2 - y1) * progress)
-- 移除 \move 标签并应用当前坐标
local clean_text = event.text:gsub("\\move%(.-%)", "")
if current_y > displayarea then return end
if event.style ~= "SP" and event.style ~= "MSG" then
return string.format("{\\pos(%.1f,%.1f)\\an8}%s", current_x, current_y, clean_text)
else
return string.format("{\\pos(%.1f,%.1f)\\an7}%s", current_x, current_y, clean_text)
end
end
function render(pos_arg)
if COMMENTS == nil then return end
local pos, err
if pos_arg == nil then
pos, err = mp.get_property_number('time-pos')
if err ~= nil then
return msg.error(err)
end
else
pos = pos_arg
end
if not pos then
overlay:remove()
return
end
local fontname = options.fontname
local fontsize = options.fontsize
local opacity = tonumber(options.opacity)
local alpha = string.format("%02X", (1 - (opacity or 0)) * 255)
local width, height = 1920, 1080
local ratio = osd_width / osd_height
if width / height < ratio then
height = width / ratio
fontsize = options.fontsize - ratio * 2
end
local ass_events = {}
local max_display = math.max(options.scrolltime, options.fixtime)
local window_start = pos - max_display
-- 跳过已结束的弹幕
local lo = binary_search(COMMENTS, window_start, function(item) return item.start_time end)
local re_entity = "&#%d+;"
local re_fs = "\\fs(%d+)"
local ass_prefix = string.format("{\\rDefault\\fn%s\\fs%d\\c&HFFFFFF&\\alpha&H%s\\bord%s\\shad%s\\b%s\\q2}",
fontname, fontsize, alpha, options.outline, options.shadow, options.bold and "1" or "0")
for i = lo, #COMMENTS do
local event = COMMENTS[i]
if not event then break end
if event.start_time > pos then break end -- 后续弹幕提前退出
if event.end_time >= pos then
local text = realtime_position_text(event, pos, height * options.displayarea)
if text then
text = text:gsub(re_entity, "")
end
if text and text:match(re_fs) then
text = text:gsub(re_fs, function(size)
local n = tonumber(size) or 0
return string.format("\\fs%d", math.floor(n * 1.5))
end)
end
-- 构建 ASS 字符串
local ass_text = text and (ass_prefix .. text)
table.insert(ass_events, ass_text)
end
end
overlay.res_x = width
overlay.res_y = height
overlay.data = table.concat(ass_events, '\n')
overlay:update()
end
local function time_pos_callback(_, time_pos)
if time_pos then
render(time_pos)
else
overlay:remove()
end
end
local function start_time_observer()
if not time_pos_observer_active then
mp.observe_property('time-pos', 'number', time_pos_callback)
time_pos_observer_active = true
end
end
local function stop_time_observer()
if time_pos_observer_active then
mp.unobserve_property(time_pos_callback)
time_pos_observer_active = false
end
end
function render_danmaku(from_menu, no_osd)
if ENABLED and (from_menu or get_danmaku_visibility()) then
if not no_osd then
show_loaded(true)
end
mp.commandv("script-message-to", "uosc", "set", "show_danmaku", "on")
show_danmaku_func()
else
show_message("")
hide_danmaku_func()
end
end
local function filter_state(label, name)
local filters = mp.get_property_native("vf")
for _, filter in pairs(filters) do
if filter.label == label or filter.name == name
or filter.params[name] ~= nil then
return true
end
end
return false
end
function show_danmaku_func()
mp.set_property_bool(HAS_DANMAKU, true)
set_danmaku_visibility(true)
render()
if not pause then
start_time_observer()
end
if options.vf_fps then
local display_fps = mp.get_property_number('display-fps')
local video_fps = mp.get_property_number('estimated-vf-fps')
if (display_fps and display_fps < 58) or (video_fps and video_fps > 58) then
return
end
if not filter_state("danmaku", "fps") then
mp.commandv("vf", "append", string.format("@danmaku:fps=fps=%s", options.fps))
end
end
end
function hide_danmaku_func()
stop_time_observer()
mp.set_property_bool(HAS_DANMAKU, false)
set_danmaku_visibility(false)
overlay:remove()
if filter_state("danmaku") then
mp.commandv("vf", "remove", "@danmaku")
end
end
local message_overlay = mp.create_osd_overlay('ass-events')
local message_timer = mp.add_timeout(3, function()
message_overlay:remove()
end, true)
function show_message(text, time)
message_timer.timeout = time or 3
message_timer:kill()
message_overlay:remove()
local message = string.format("{\\an%d\\pos(%d,%d)}%s", options.message_anlignment,
options.message_x, options.message_y, text)
local width, height = 1920, 1080
local ratio = osd_width / osd_height
if width / height < ratio then
height = width / ratio
end
message_overlay.res_x = width
message_overlay.res_y = height
message_overlay.data = message
message_overlay:update()
message_timer:resume()
end
mp.observe_property('osd-width', 'number', function(_, value) osd_width = value or osd_width end)
mp.observe_property('osd-height', 'number', function(_, value) osd_height = value or osd_height end)
mp.observe_property('pause', 'bool', function(_, value)
if value ~= nil then
pause = value
end
if ENABLED then
if pause then
stop_time_observer()
elseif COMMENTS ~= nil then
start_time_observer()
end
end
end)
mp.register_event('playback-restart', function(event)
if event.error then
return msg.error(event.error)
end
if ENABLED and COMMENTS ~= nil then
render()
end
end)
mp.add_hook("on_unload", 50, function()
COMMENTS, DELAY = nil, 0
stop_time_observer()
overlay:remove()
mp.set_property_native(DELAY_PROPERTY, 0)
if filter_state("danmaku") then
mp.commandv("vf", "remove", "@danmaku")
end
local files_to_remove = {
file1 = utils.join_path(DANMAKU_PATH, "temp-" .. PID .. ".mp4"),
}
if options.save_danmaku then
save_danmaku(true)
end
for _, file in pairs(files_to_remove) do
if file_exists(file) then
os.remove(file)
end
end
DANMAKU = {sources = {}, count = 1}
end)
+167
View File
@@ -0,0 +1,167 @@
local msg = require('mp.msg')
local utils = require("mp.utils")
local repo = "Tony15246/uosc_danmaku"
local zip_file = utils.join_path(os.getenv("TEMP") or "/tmp/", "uosc_danmaku.zip")
local local_version = VERSION or "0.0.0"
local platform = mp.get_property("platform")
local function version_greater(v1, v2)
local function parse(ver)
local a, b, c = ver:match("v?(%d+)%.(%d+)%.(%d+)")
return tonumber(a), tonumber(b), tonumber(c)
end
local a1, a2, a3 = parse(v1)
local b1, b2, b3 = parse(v2)
if a1 ~= b1 then return a1 > b1 end
if a2 ~= b2 then return a2 > b2 end
return a3 > b3
end
local function get_latest_release(repo)
local url = "https://api.github.com/repos/" .. repo .. "/releases/latest"
local cmd = { "curl", "-sL", url }
local res = mp.command_native({
name = "subprocess",
args = cmd,
capture_stdout = true,
capture_stderr = true,
playback_only = false,
})
if not res or res.status ~= 0 then return nil end
local tag = res.stdout:match([["tag_name"%s*:%s*"([^"]+)"]])
local zip_url = res.stdout:match([["browser_download_url"%s*:%s*"([^"]+%.zip)"]])
return tag, zip_url
end
local function escape_ps(str)
return tostring(str):gsub("'", "''")
end
local function unzip_overwrite(zip_file)
local outpath = mp.get_script_directory()
-- 定义临时目录路径,用于安全更新
local tmpdir = utils.join_path(
(platform == "windows" and (os.getenv("TEMP") or "C:\\Windows\\Temp") or "/tmp"),
"uosc_update_" .. tostring(os.time())
)
local cmd_unzip = {}
msg.info("创建临时目录并解压: " .. tmpdir)
if platform == "windows" then
-- PowerShell: Expand-Archive (会自动创建目标目录)
local ps_script = string.format(
"Expand-Archive -LiteralPath '%s' -DestinationPath '%s' -Force",
escape_ps(zip_file),
escape_ps(tmpdir)
)
cmd_unzip = { "powershell", "-NoProfile", "-Command", ps_script }
else
-- Unix: unzip
cmd_unzip = { "unzip", "-o", zip_file, "-d", tmpdir }
end
local res = mp.command_native({
name = "subprocess",
args = cmd_unzip,
capture_stdout = true,
capture_stderr = true,
playback_only = false,
})
if not res or res.status ~= 0 then
msg.error("❌ 解压失败:\n" .. (res and (res.stdout .. res.stderr) or "未知错误"))
-- 清理残留的临时目录
if platform == "windows" then
mp.command_native({
name = "subprocess",
args = {"powershell", "-NoProfile", "-Command", "Remove-Item -LiteralPath '"..escape_ps(tmpdir).."' -Recurse -Force"}
})
else
os.execute("rm -rf \"" .. tmpdir .. "\"")
end
return false
end
msg.info("解压成功,准备替换旧目录...")
local cmd_swap = {}
if platform == "windows" then
-- Windows: 在一个 PowerShell 实例中执行删除和移动
local ps_swap = string.format(
"Remove-Item -LiteralPath '%s' -Recurse -Force -ErrorAction SilentlyContinue; Move-Item -LiteralPath '%s' -Destination '%s' -Force",
escape_ps(outpath),
escape_ps(tmpdir),
escape_ps(outpath)
)
cmd_swap = { "powershell", "-NoProfile", "-Command", ps_swap }
else
-- Unix: rm && mv
cmd_swap = { "sh", "-c", string.format("rm -rf \"%s\" && mv \"%s\" \"%s\"", outpath, tmpdir, outpath) }
end
local res_swap = mp.command_native({
name = "subprocess",
args = cmd_swap,
capture_stdout = true,
capture_stderr = true,
playback_only = false,
})
if not res_swap or res_swap.status ~= 0 then
msg.error("❌ 替换目录失败:\n" .. (res_swap and (res_swap.stdout .. res_swap.stderr) or ""))
return false
end
msg.info("更新完成")
return true
end
function check_for_update()
local latest_version, download_url = get_latest_release(repo)
if not latest_version or not download_url then
show_message("❌ 无法获取最新版本信息")
msg.warn("❌ 无法获取最新版本信息")
return
end
if not version_greater(latest_version, local_version) then
show_message("✅ 已是最新版本 ("..local_version..")")
msg.info("✅ 已是最新版本")
return
end
show_message("⬇️ 发现新版本: " .. latest_version .. ",正在下载...")
msg.info("⬇️ 发现新版本: " .. latest_version .. ",地址: " .. download_url)
local cmd = { "curl", "-L", "-o", zip_file, download_url }
local res = mp.command_native({
name = "subprocess",
args = cmd,
capture_stdout = true,
capture_stderr = true,
playback_only = false,
})
if not res or res.status ~= 0 then
show_message("❌ 下载失败!")
msg.warn("❌ 下载失败!")
return
end
show_message("📦 下载完成,开始解压覆盖...")
msg.info("📦 下载完成,开始解压覆盖...")
if unzip_overwrite(zip_file) then
os.remove(zip_file)
show_message("✅ 更新成功!请重启 mpv 以应用更新,当前版本为:" .. latest_version)
msg.info("✅ 更新成功,当前版本为:" .. latest_version)
else
os.remove(zip_file)
show_message("❌ 解压失败!请查看控制台日志")
msg.warn("❌ 解压失败!")
end
end
+755
View File
@@ -0,0 +1,755 @@
local utils = require("mp.utils")
local unpack = unpack or table.unpack
-- from http://lua-users.org/wiki/LuaUnicode
local UTF8_PATTERN = '[%z\1-\127\194-\244][\128-\191]*'
-- return a substring based on utf8 characters
-- like string.sub, but negative index is not supported
function utf8_sub(s, i, j)
if i > j then
return s
end
local t, idx = {}, 1
for char in s:gmatch(UTF8_PATTERN) do
if idx >= i and idx <= j then
t[#t + 1] = char
end
idx = idx + 1
end
return table.concat(t)
end
function utf8_len(s)
local count = 0
for _ in s:gmatch(UTF8_PATTERN) do
count = count + 1
end
return count
end
function utf8_iter(s)
local iter = s:gmatch(UTF8_PATTERN)
return function()
return iter()
end
end
function utf8_to_table(s)
local t = {}
for ch in utf8_iter(s) do
t[#t + 1] = ch
end
return t
end
-- abbreviate string if it's too long
function abbr_str(str, length)
if not str or str == '' then return '' end
local str_clip = utf8_sub(str, 1, length)
if str ~= str_clip then
return str_clip .. '...'
end
return str
end
function get_str_width(text, font_size)
local width = 0
for i = 1, #text do
local byte = string.byte(text, i)
if byte > 127 then
width = width + 2
else
width = width + 1
end
end
local unicode_width = 0
local i = 1
while i <= #text do
local byte = string.byte(text, i)
local char_len
if byte < 128 then char_len = 1; unicode_width = unicode_width + 1
elseif byte >= 192 and byte < 224 then char_len = 2; unicode_width = unicode_width + 2
elseif byte >= 224 and byte < 240 then char_len = 3; unicode_width = unicode_width + 2
elseif byte >= 240 and byte < 248 then char_len = 4; unicode_width = unicode_width + 2
else char_len = 1; unicode_width = unicode_width + 1
end
i = i + char_len
end
return unicode_width * (font_size / 2)
end
function unicode_to_utf8(unicode)
if unicode < 0x80 then
return string.char(unicode)
else
local byte_count
if unicode < 0x800 then
byte_count = 2
elseif unicode < 0x10000 then
byte_count = 3
elseif unicode < 0x110000 then
byte_count = 4
else
return
end
local res = {}
local shift = 2 ^ 6
local after_shift = unicode
for _ = byte_count, 2, -1 do
local before_shift = after_shift
after_shift = math.floor(before_shift / shift)
table.insert(res, 1, before_shift - after_shift * shift + 0x80)
end
shift = 2 ^ (8 - byte_count)
table.insert(res, 1, after_shift + math.floor(0xFF / shift) * shift)
---@diagnostic disable-next-line: deprecated
return string.char(unpack(res))
end
end
function jaro(s1, s2)
local match_window = math.floor(math.max(#s1, #s2) / 2.0) - 1
local matches1 = {}
local matches2 = {}
local m = 0;
local t = 0;
for i = 0, #s1, 1 do
local start = math.max(0, i - match_window)
local final = math.min(i + match_window + 1, #s2)
for k = start, final, 1 do
if not (matches2[k] or s1[i] ~= s2[k]) then
matches1[i] = true
matches2[k] = true
m = m + 1
break
end
end
end
if m == 0 then
return 0.0
end
local k = 0
for i = 0, #s1, 1 do
if matches1[i] then
while not matches2[k] do
k = k + 1
end
if s1[i] ~= s2[k] then
t = t + 1
end
k = k + 1
end
end
t = t / 2.0
return (m / #s1 + m / #s2 + (m - t) / m) / 3.0
end
function jaro_winkler(s1, s2)
if #s1 + #s2 == 0 then
return 0.0
end
if s1 == s2 then
return 1.0
end
s1 = utf8_to_table(s1)
s2 = utf8_to_table(s2)
local d = jaro(s1, s2)
local p = 0.1
local l = 0;
while (s1[l] == s2[l] and l < 4) do
l = l + 1
end
return d + l * p * (1 - d)
end
-- 从时间字符串转换为秒数
function time_to_seconds(time_str)
local h, m, s = time_str:match("(%d+):(%d+):([%d%.]+)")
return tonumber(h) * 3600 + tonumber(m) * 60 + tonumber(s)
end
-- 从秒数转换为时间字符串
function seconds_to_time(seconds)
local hours = math.floor(seconds / 3600)
local minutes = math.floor((seconds % 3600) / 60)
local secs = math.floor(seconds % 60)
local centiseconds = math.floor((seconds - math.floor(seconds)) * 100)
return string.format("%d:%02d:%02d.%02d", hours, minutes, secs, centiseconds)
end
function is_chinese(str)
return string.match(str, "[\228-\233][\128-\191]") ~= nil
end
function is_protocol(path)
return type(path) == 'string' and (path:find('^%a[%w.+-]-://') ~= nil or path:find('^%a[%w.+-]-:%?') ~= nil)
end
function hex_to_bin(hexstr)
return (hexstr:gsub('..', function (cc)
return string.char(tonumber(cc, 16))
end))
end
function hex_to_char(x)
return string.char(tonumber(x, 16))
end
function hex_to_int_color(hex_color)
-- 移除颜色代码中的'#'字符
hex_color = hex_color:sub(2) -- 只保留颜色代码部分
-- 提取R, G, B的十六进制值并转为整数
local r = tonumber(hex_color:sub(1, 2), 16)
local g = tonumber(hex_color:sub(3, 4), 16)
local b = tonumber(hex_color:sub(5, 6), 16)
-- 计算32位整数值
local color_int = (r * 256 * 256) + (g * 256) + b
return color_int
end
local function get_type_from_position(position)
if position == 0 then
return 1
end
if position == 1 then
return 4
end
return 5
end
-- url编码转换
function url_encode(str)
-- 将非安全字符转换为百分号编码
if str then
str = str:gsub("([^%w%-%.%_%~])", function(c)
return string.format("%%%02X", string.byte(c))
end)
end
return str
end
-- url解码转换
function url_decode(str)
if str ~= nil then
str = str:gsub('^%a[%a%d-_]+://', '')
:gsub('^%a[%a%d-_]+:\\?', '')
:gsub('%%(%x%x)', hex_to_char)
if str:find('://localhost:?') then
str = str:gsub('^.*/', '')
end
str = str:gsub('%?.+', '')
:gsub('%+', ' ')
return str
end
end
-- Utility function to split a string by a delimiter
function split(str, delim)
local result = {}
for match in (str .. delim):gmatch("(.-)" .. delim) do
table.insert(result, match)
end
return result
end
function table_to_zero_indexed(tbl)
for i = #tbl, 1, -1 do
tbl[i - 1] = tbl[i]
end
tbl[#tbl] = nil
return tbl
end
function itable_index_of(itable, value)
for index = 1, #itable do
if itable[index] == value then
return index
end
end
end
function is_nested_table(t)
if type(t) ~= "table" then
return false
end
for _, v in pairs(t) do
if type(v) == "table" then
return true
end
end
return false
end
function shallow_copy(original)
if type(original) ~= "table" then
return original
end
local copy = {}
for k, v in pairs(original) do
copy[k] = v
end
return copy
end
function deep_copy(obj, seen)
if type(obj) ~= "table" then
return obj
end
if seen and seen[obj] then
return seen[obj]
end
local s = seen or {}
local copy = {}
s[obj] = copy
for k, v in pairs(obj) do
copy[deep_copy(k, s)] = deep_copy(v, s)
end
setmetatable(copy, getmetatable(obj))
return copy
end
function remove_query(url)
local qpos = string.find(url, "?", 1, true)
if qpos then
return string.sub(url, 1, qpos - 1)
else
return url
end
end
function file_exists(path)
if path then
local meta = utils.file_info(path)
return meta and meta.is_file
end
return false
end
function binary_search(tbl, target, key)
if not tbl or #tbl == 0 then return 1 end
key = key or function(x) return x end
local lo, hi = 1, #tbl
local res = #tbl + 1
while lo <= hi do
local mid = math.floor((lo + hi) / 2)
local v = tbl[mid]
local val = key(v)
if val >= target then
res = mid
hi = mid - 1
else
lo = mid + 1
end
end
return res
end
function new_min_heap()
local h = {}
local function swap(i, j)
h[i], h[j] = h[j], h[i]
end
local function up(i)
while i > 1 do
local p = math.floor(i/2)
if h[p].time <= h[i].time then break end
swap(p, i)
i = p
end
end
local function down(i)
local n = #h
while true do
local l = i * 2
local r = l + 1
local smallest = i
if l <= n and h[l].time < h[smallest].time then smallest = l end
if r <= n and h[r].time < h[smallest].time then smallest = r end
if smallest == i then break end
swap(i, smallest)
i = smallest
end
end
local function push(node)
h[#h + 1] = node
up(#h)
end
local function pop()
if #h == 0 then return nil end
local root = h[1]
if #h == 1 then h[1] = nil; return root end
h[1] = h[#h]
h[#h] = nil
down(1)
return root
end
return { push = push, pop = pop, size = function() return #h end }
end
function is_writable(path)
local file = io.open(path, "w")
if file then
file:close()
os.remove(path)
return true
end
return false
end
function contains_any(tab, val)
for _, element in pairs(tab) do
if string.find(val, element) then
return true
end
end
return false
end
--读history 和 写history
function read_file(file_path)
local file = io.open(file_path, "r") -- 打开文件,"r"表示只读模式
if not file then
return nil
end
local content = file:read("*all") -- 读取文件所有内容
file:close() -- 关闭文件
return content
end
-- 应用额外的自定义标题替换规则
function title_replace(title)
local title_replace = utils.parse_json(options.title_replace)
if not title_replace then
return title
end
for _, v in pairs(title_replace) do
for _, indexrules in pairs(v['rules']) do
for rule, override in pairs(indexrules) do
title = title:gsub(rule, override)
:gsub("[_%.]", " ")
:gsub("^%s*(.-)%s*$", "%1")
:gsub("[@#%.%+%-%%&*_=,/~`]+$", "")
end
end
end
return title
end
function write_json_file(file_path, data)
local file = io.open(file_path, "w")
if not file then
return
end
file:write(utils.format_json(data)) -- 将 Lua 表转换为 JSON 并写入
file:close()
end
-- 拆分字符串中的字符和数字
local function split_by_numbers(filename)
local parts = {}
local pattern = "([^%d]*)(%d+)([^%d]*)"
for pre, num, post in string.gmatch(filename, pattern) do
table.insert(parts, {pre = pre, num = tonumber(num), post = post})
end
return parts
end
-- 识别匹配前后剧集并提取集数
local function get_series_episodes(fname1, fname2)
local parts1 = split_by_numbers(fname1)
local parts2 = split_by_numbers(fname2)
local title1 = format_filename(fname1)
local title2 = format_filename(fname2)
if title1 and title2 then
local media_title1, season1, episode1 = title1:match("^(.-)%s*[sS](%d+)[eE](%d+)")
local media_title2, season2, episode2 = title2:match("^(.-)%s*[sS](%d+)[eE](%d+)")
if season1 and season2 and season1 ~= season2 then
return nil, nil
end
end
local min_len = math.min(#parts1, #parts2)
-- 逐个部分进行比较
for i = 1, min_len do
local part1 = parts1[i]
local part2 = parts2[i]
-- 比较数字前的字符是否相同
if part1.pre ~= part2.pre then
return nil, nil
end
-- 比较数字部分
if part1.num ~= part2.num then
return part1.num, part2.num
end
-- 比较数字后的字符是否相同
if part1.post ~= part2.post then
return nil, nil
end
end
return nil, nil
end
-- 获取当前文件名所包含的集数
function get_episode_number(filename, fname)
-- 尝试对比记录文件名来获取当前集数
if fname then
return get_series_episodes(fname, filename)
end
local thin_space = string.char(0xE2, 0x80, 0x89)
filename = filename:gsub(thin_space, " ")
local title = format_filename(filename)
if title then
local media_title, season, episode = title:match("^(.-)%s*[sS](%d+)[eE](%d+)")
if season then
return tonumber(episode)
else
local media_title, episode = title:match("^(.-)%s*[eE](%d+)")
if episode then
return tonumber(episode)
end
end
end
return nil
end
-- 规范化路径
function normalize(path)
if normalize_path ~= nil then
if normalize_path then
path = mp.command_native({"normalize-path", path})
else
local directory = mp.get_property("working-directory", "")
path = utils.join_path(directory, path:gsub('^%.[\\/]',''))
if PLATFORM == "windows" then path = path:gsub("\\", "/") end
end
return path
end
normalize_path = false
local commands = mp.get_property_native("command-list", {})
for _, command in ipairs(commands) do
if command.name == "normalize-path" then
normalize_path = true
break
end
end
return normalize(path)
end
-- 获取父目录路径
function get_parent_directory(path)
local dir = nil
if path and not is_protocol(path) then
path = normalize(path)
dir = utils.split_path(path)
end
return dir
end
-- 获取播放文件标题信息
function parse_title()
local path = mp.get_property("path")
local filename = mp.get_property("filename/no-ext")
if not filename then
return
end
local thin_space = string.char(0xE2, 0x80, 0x89)
filename = filename:gsub(thin_space, " ")
local media_title, season, episode = nil, nil, nil
if path and not is_protocol(path) then
local title = format_filename(filename)
if title then
media_title, season, episode = title:match("^(.-)%s*[sS](%d+)[eE](%d+)")
if season then
return title_replace(media_title), season, episode
else
media_title, episode = title:match("^(.-)%s*[eE](%d+)")
if episode then
return title_replace(media_title), season, episode
end
end
return title_replace(title)
end
local directory = get_parent_directory(path)
local dir, title = utils.split_path(directory:sub(1, -2))
if title:lower():match("^%s*seasons?%s*%d+%s*$") or title:lower():match("^%s*specials?%s*$") or title:match("^%s*SPs?%s*$")
or title:match("^%s*O[VAD]+s?%s*$") or title:match("^%s*第.-[季部]+%s*$") then
directory, title = utils.split_path(dir:sub(1, -2))
end
title = title
:gsub(thin_space, " ")
:gsub("%[.-%]", "")
:gsub("^%s*%(%d+.?%d*.?%d*%)", "")
:gsub("%(%d+.?%d*.?%d*%)%s*$", "")
:gsub("[%._]", " ")
:gsub("^%s*(.-)%s*$", "%1")
return title_replace(title)
end
local title = mp.get_property("media-title")
if title then
title = title:gsub(thin_space, " ")
local ftitle = url_decode(title)
local name, class = ftitle:match("^(.-)%s*|%s*(.-)%s*$")
if name then ftitle = name end
local format_title = format_filename(ftitle)
if format_title then
media_title, season, episode = format_title:match("^(.-)%s*[sS](%d+)[eE](%d+)")
if season then
title = media_title
else
media_title, episode = format_title:match("^(.-)%s*[eE](%d+)")
if episode then
season = 1
title = media_title
else
title = format_title
end
end
end
end
return title_replace(title), season, episode
end
local CHINESE_NUM_MAP = {
[""] = 0, [""] = 1, [""] = 2, [""] = 3, [""] = 4,
[""] = 5, [""] = 6, [""] = 7, [""] = 8, [""] = 9,
[""] = 10, [""] = 100, [""] = 1000, [""] = 10000,
}
function chinese_to_number(cn)
local total = 0
local num = 0
local unit = 1
local chars = {}
for uchar in cn:gmatch(UTF8_PATTERN) do
table.insert(chars, 1, uchar)
end
for _, char in ipairs(chars) do
local val = CHINESE_NUM_MAP[char]
if val then
if val >= 10 then
if num == 0 then
num = 1
end
unit = val
else
total = total + val * unit
unit = 1
num = 0
end
end
end
if unit > 1 then
total = total + num * unit
end
if total > 0 then
return total
else
return num
end
end
local CHINESE_NUM = {"", "", "", "", "", "", "", "", "", ""}
local CHINESE_UNIT = {"", "", "", ""}
local CHINESE_BIG_UNIT = {"", "", "亿"}
function number_to_chinese(num)
if num == 0 then return "" end
local str = tostring(num)
local len = #str
local result = ""
local zero_flag = false
for i = 1, len do
local digit = tonumber(str:sub(i, i))
local pos = len - i + 1
local small_unit_index = (pos - 1) % 4 + 1
local small_unit = CHINESE_UNIT[small_unit_index]
if digit == 0 then
zero_flag = true
else
if zero_flag then
result = result .. ""
zero_flag = false
end
if digit == 1 and small_unit_index == 2 and i == 1 then
result = result .. small_unit
else
result = result .. CHINESE_NUM[digit + 1] .. small_unit
end
end
if pos % 4 == 1 and pos > 1 then
local big_unit_index = math.floor((pos - 1) / 4)
result = result .. CHINESE_BIG_UNIT[big_unit_index + 1]
end
end
result = result:gsub("零+$", "")
return result
end
-- 异步执行命令
-- 同时返回 abort 函数,用于取消异步命令
function call_cmd_async(args, callback)
async_running = true
local abort_signal = mp.command_native_async({
name = 'subprocess',
capture_stderr = true,
capture_stdout = true,
playback_only = true,
args = args,
}, function(success, result, error)
if not success or not result or result.status ~= 0 then
local exit_code = (result and result.status or 'unknown')
local message = error or (result and result.stdout .. result.stderr) or ''
callback('Calling failed. Exit code: ' .. exit_code .. ' Error: ' .. message, {})
return
end
local json = result and type(result.stdout) == 'string' and result.stdout or ''
return callback(nil, json)
end)
return function()
mp.abort_async_command(abort_signal)
end
end