first commit
BIN
app/__pycache__/ext.cpython-310.pyc
Normal file
BIN
app/__pycache__/loadmedia.cpython-310.pyc
Normal file
30
app/add_imageinfo.py
Normal file
@ -0,0 +1,30 @@
|
||||
import os, sys
|
||||
import asyncio
|
||||
from appPublic.jsonConfig import getConfig
|
||||
from sqlor.dbpools import DBPools
|
||||
from ahserver.filestorage import FileStorage
|
||||
from loadmedia import save_image_info
|
||||
|
||||
async def main():
|
||||
workdir = os.path.join(os.environ.get('HOME'), 'py', 'media')
|
||||
config = getConfig(workdir, {'workdir':workdir})
|
||||
imgs = []
|
||||
fs = FileStorage()
|
||||
db = DBPools(config.databases)
|
||||
async with db.sqlorContext('mediadb') as sor:
|
||||
sql = """select a.* from media a left join imageinfo b
|
||||
on a.id = b.id
|
||||
where a.mtype = 'image'
|
||||
and b.id is NULL
|
||||
order by ownerid
|
||||
"""
|
||||
imgs = await sor.sqlExe(sql, {})
|
||||
cnt = len(imgs)
|
||||
for i, img in enumerate(imgs):
|
||||
async with db.sqlorContext('mediadb') as sor:
|
||||
print(f'handle {img.mlocation} {i}/{cnt}')
|
||||
path = fs.realPath(img.mlocation)
|
||||
await save_image_info(sor, img.ownerid, img.id, path, img.ownerid)
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.get_event_loop().run_until_complete(main())
|
59
app/ext.py
Normal file
@ -0,0 +1,59 @@
|
||||
import json
|
||||
from traceback import print_exc
|
||||
from ahserver.serverenv import ServerEnv
|
||||
from ahserver.filestorage import FileStorage
|
||||
from sqlor.dbpools import DBPools
|
||||
from appPublic.timeUtils import curDateString, timestampstr
|
||||
from appPublic.dictObject import DictObject
|
||||
from appPublic.log import info, debug, warning, error, exception, critical
|
||||
from appPublic.uniqueID import getID
|
||||
from appPublic.registerfunction import RegisterFunction, rfexe
|
||||
|
||||
|
||||
def UiWindow(title, icon, content, cheight=10, cwidth=15):
|
||||
return {
|
||||
"widgettype":"PopupWindow",
|
||||
"options":{
|
||||
"author":"cc",
|
||||
"cwidth":cwidth,
|
||||
"cheight":cheight,
|
||||
"title":title,
|
||||
"content":content,
|
||||
"icon":icon or entire_url('/bricks/imgs/app.png'),
|
||||
"movable":True,
|
||||
"auto_open":True
|
||||
}
|
||||
}
|
||||
|
||||
def UiError(title="出错", message="出错啦", timeout=5):
|
||||
return {
|
||||
"widgettype":"Error",
|
||||
"options":{
|
||||
"author":"tr",
|
||||
"timeout":timeout,
|
||||
"cwidth":15,
|
||||
"cheight":10,
|
||||
"title":title,
|
||||
"message":message
|
||||
}
|
||||
}
|
||||
|
||||
def UiMessage(title="消息", message="后台消息", timeout=5):
|
||||
return {
|
||||
"widgettype":"Message",
|
||||
"options":{
|
||||
"author":"tr",
|
||||
"timeout":timeout,
|
||||
"cwidth":15,
|
||||
"cheight":10,
|
||||
"title":title,
|
||||
"message":message
|
||||
}
|
||||
}
|
||||
|
||||
def init_ui_ext():
|
||||
g = ServerEnv()
|
||||
g.UiError = UiError
|
||||
g.UiMessage = UiMessage
|
||||
g.UiWindow = UiWindow
|
||||
|
47
app/imageface.py
Normal file
@ -0,0 +1,47 @@
|
||||
from appPublic.worker import awaitify
|
||||
from appPublic.log import debug, exception
|
||||
from appPublic.uniqueID import getID
|
||||
from sqlor.dbpools import DBPools
|
||||
from ahserver.filestorage import FileStorage
|
||||
import face_recognition
|
||||
from PIL import Image
|
||||
|
||||
async def create_knownface(imgid, logo_url, name):
|
||||
db = DBPools()
|
||||
dbname = 'mediadb'
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
return await _create_knownface(sor, imgid, logo_url, name)
|
||||
e = Exception(f'{imgid=}, {logo_url=}, {name=}:create_knownface() error')
|
||||
exception(f'{e=}')
|
||||
raise e
|
||||
|
||||
async def _create_knownface(sor, imgid, logo_url, name):
|
||||
fs = FileStorage()
|
||||
imgpath = fs.realPath(logo_url)
|
||||
imgobj = await awaitify(face_recognition.load_image_file)(imgpath)
|
||||
infos = await awaitify(face_recognition.face_encodings)(imgobj)
|
||||
locs = await awaitify(face_recognition.face_locations)(imgobj)
|
||||
if len(locs) > 1:
|
||||
e = Exception(f'{imgpath} found more than one face')
|
||||
exception(f'{e=}')
|
||||
raise e
|
||||
if len(locs) == 0:
|
||||
e = Exception(f'{imgpath} found not face')
|
||||
exception(f'{e=}')
|
||||
raise e
|
||||
l = locs[0]
|
||||
faceinfo = json.dumps({
|
||||
'feature':infos[0],
|
||||
"face_top":top,
|
||||
"face_bottom":bottom,
|
||||
"face_left":left,
|
||||
"face_right":right
|
||||
})
|
||||
ns = {
|
||||
"id":getID(),
|
||||
"mediaid":imgid,
|
||||
"faceinfo":faceinfo,
|
||||
"personname":name
|
||||
}
|
||||
await sor.C('photofaces', ns)
|
||||
|
89
app/imageinfo.py
Normal file
@ -0,0 +1,89 @@
|
||||
from PIL import Image, ExifTags
|
||||
import exifread
|
||||
import pyheif
|
||||
from pillow_heif import register_heif_opener
|
||||
from findperson.imageface import ImageFaces
|
||||
register_heif_opener()
|
||||
|
||||
ifs = None
|
||||
|
||||
def get_imagefaces(imgfile, imgid, userid):
|
||||
global ifs
|
||||
if ifs is None:
|
||||
ifs = ImageFaces()
|
||||
|
||||
info = ifs.save_faces(userid, imgfile, imgid=imgid)
|
||||
return info
|
||||
|
||||
def get_heif_exif(imgfile):
|
||||
# 读取HEIC文件
|
||||
heif_file = pyheif.read(imgfile)
|
||||
|
||||
# 将HEIC转换为PIL图像对象
|
||||
image = Image.frombytes(
|
||||
heif_file.mode,
|
||||
heif_file.size,
|
||||
heif_file.data,
|
||||
"raw",
|
||||
heif_file.mode,
|
||||
heif_file.stride,
|
||||
)
|
||||
|
||||
# 提取EXIF数据
|
||||
exif_data = image.getexif()
|
||||
|
||||
# 将EXIF数据转换为可读格式
|
||||
exif_data_dict = {
|
||||
ExifTags.TAGS.get(k, k): v
|
||||
for k, v in exif_data.items()
|
||||
if k in ExifTags.TAGS
|
||||
}
|
||||
return exif_data_dict
|
||||
|
||||
def convert_to_degrees(value):
|
||||
d = float(value.values[0].num) / float(value.values[0].den)
|
||||
m = float(value.values[1].num) / float(value.values[1].den)
|
||||
s = float(value.values[2].num) / float(value.values[2].den)
|
||||
|
||||
return d + (m / 60.0) + (s / 3600.0)
|
||||
|
||||
def get_image_info(imgfile):
|
||||
"""
|
||||
从EXIF数据中提取纬度和经度信息。
|
||||
"""
|
||||
exif_data = None
|
||||
if imgfile.lower().endswith('heic'):
|
||||
exif_data = get_heif_exif(imgfile)
|
||||
else:
|
||||
with open(imgfile, 'rb') as f:
|
||||
exif_data = exifread.process_file(f)
|
||||
if exif_data is None:
|
||||
return None, None, None
|
||||
# 经度
|
||||
timestamp = None
|
||||
if 'EXIF DateTimeOriginal' in exif_data:
|
||||
timestamp = str(exif_data['EXIF DateTimeOriginal'])
|
||||
timestamp = timestamp[:19]
|
||||
lon_ref = exif_data.get('GPS GPSLongitudeRef')
|
||||
lon = exif_data.get('GPS GPSLongitude')
|
||||
lat_ref = exif_data.get('GPS GPSLatitudeRef')
|
||||
lat = exif_data.get('GPS GPSLatitude')
|
||||
glon = None
|
||||
glat = None
|
||||
try:
|
||||
if lon_ref and lon:
|
||||
glon = convert_to_degrees(lon)
|
||||
if lon_ref.values == 'W':
|
||||
glon = -lon
|
||||
|
||||
# 纬度
|
||||
if lat_ref and lat:
|
||||
glat = convert_to_degrees(lat)
|
||||
if lat_ref.values == 'S':
|
||||
glat = -lat
|
||||
except:
|
||||
pass
|
||||
print(f'{imgfile=}, {glat=}, {glon=}, {timestamp=}')
|
||||
|
||||
return glat, glon, timestamp
|
||||
|
215
app/loadmedia.py
Normal file
@ -0,0 +1,215 @@
|
||||
|
||||
import os, sys
|
||||
import json
|
||||
import asyncio
|
||||
import argparse
|
||||
import fingerprint
|
||||
from traceback import format_exc
|
||||
from sqlor.dbpools import DBPools
|
||||
from appPublic.jsonConfig import getConfig
|
||||
from appPublic.log import debug, exception
|
||||
from appPublic.worker import awaitify
|
||||
from appPublic.dictObject import DictObject
|
||||
from appPublic.timeUtils import curDateString, timestampstr
|
||||
from appPublic.uniqueID import getID
|
||||
from appPublic.folderUtils import listFile, _mkdir
|
||||
from ahserver.filestorage import FileStorage
|
||||
import av
|
||||
from PIL import Image
|
||||
from imageinfo import get_image_info, get_imagefaces
|
||||
|
||||
from pillow_heif import register_heif_opener
|
||||
register_heif_opener()
|
||||
|
||||
def get_thumbnail(mloc, loctype, userid):
|
||||
fs = FileStorage()
|
||||
if loctype == '0': # webpath
|
||||
mloc1 = fs.realPath(mloc)
|
||||
mloc = mloc1
|
||||
# Open the original image
|
||||
with Image.open(mloc) as img:
|
||||
# Define the size of the thumbnail
|
||||
thumbnail_size = (256, 256) # for example, 128x128 pixels
|
||||
# Create the thumbnail
|
||||
img.thumbnail(thumbnail_size)
|
||||
name = os.path.basename(mloc).split('.')[0] + '.jpg'
|
||||
fn = fs._name2path(name, userid=userid)
|
||||
webpath = fs.webpath(fn)
|
||||
_mkdir(os.path.dirname(fn))
|
||||
|
||||
# Save the thumbnail to disk
|
||||
img.save(fn, 'JPEG')
|
||||
return webpath
|
||||
|
||||
def get_1st_keyframe(mloc, loctype, userid):
|
||||
# Open the video file
|
||||
fs = FileStorage()
|
||||
if loctype == '0': # webpath
|
||||
mloc1 = fs.realPath(mloc)
|
||||
mloc = mloc1
|
||||
container = av.open(mloc)
|
||||
# Iterate through the video to find the first key frame
|
||||
kcnt = 0
|
||||
for frame in container.decode(video=0):
|
||||
if frame.key_frame:
|
||||
kcnt += 1
|
||||
if kcnt > 10:
|
||||
# Save the key frame as an image
|
||||
name = os.path.basename(mloc).split('.')[0] + '.jpg'
|
||||
fn = fs._name2path(name, userid=userid)
|
||||
webpath = fs.webpath(fn)
|
||||
_mkdir(os.path.dirname(fn))
|
||||
frame.to_image().save(fn)
|
||||
return webpath
|
||||
|
||||
async def save_image_info(sor, userid, imgid, path, ownerid):
|
||||
f = awaitify(get_image_info)
|
||||
lat, lon, ts = await f(path)
|
||||
finfo = await awaitify(get_imagefaces)(path, imgid, ownerid)
|
||||
ns1 = {
|
||||
"id":imgid
|
||||
}
|
||||
if ts is None:
|
||||
ns1["info_status"] = '0'
|
||||
else:
|
||||
ns1["info_status"] = "1"
|
||||
ns1["latitude"] = lat
|
||||
ns1["longitude"] = lon
|
||||
ns1["media_timestamp"] = ts
|
||||
ns1["face_cnt"] = len(finfo['faces'])
|
||||
await sor.C('imageinfo', ns1.copy())
|
||||
for f in finfo['faces']:
|
||||
ns2 = {
|
||||
'id': getID(),
|
||||
'imageid':imgid,
|
||||
'faceid':f['id']
|
||||
}
|
||||
await sor.C('imageface', ns2)
|
||||
|
||||
async def save_image(sor, path, webpath, ownerid):
|
||||
ns = DictObject()
|
||||
ns.id = getID()
|
||||
ns.name = os.path.basename(path).rsplit('.', 1)[0]
|
||||
ns.mtype = 'image'
|
||||
ns.mlocation = webpath
|
||||
ns.mloctype = '0'
|
||||
ns.logo_url = get_thumbnail(ns.mlocation, ns.mloctype, ownerid)
|
||||
ns.create_date = curDateString()
|
||||
ns.ownerid = ownerid
|
||||
ns.del_flg = '0'
|
||||
refer_cnt = 1
|
||||
await sor.C('media', ns.copy())
|
||||
# await save_image_info(sor, ns.id, path, ownerid)
|
||||
return
|
||||
|
||||
async def save_audio(sor, path, webpath, ownerid):
|
||||
ns = DictObject()
|
||||
ns.id = getID()
|
||||
ns.name = os.path.basename(path).rsplit('.', 1)[0]
|
||||
ns.mtype = 'audio'
|
||||
ns.mlocation = webpath
|
||||
ns.mloctype = '0'
|
||||
ns.create_date = curDateString()
|
||||
ns.ownerid = ownerid
|
||||
ns.del_flg = '0'
|
||||
refer_cnt = 1
|
||||
await sor.C('media', ns.copy())
|
||||
|
||||
async def save_video(sor, path, webpath, ownerid):
|
||||
ns = DictObject()
|
||||
ns.id = getID()
|
||||
ns.name = os.path.basename(path).rsplit('.', 1)[0]
|
||||
ns.mtype = 'video'
|
||||
ns.mlocation = webpath
|
||||
ns.mloctype = '0'
|
||||
ns.logo_url = get_1st_keyframe(ns.mlocation, ns.mloctype, ownerid)
|
||||
ns.create_date = curDateString()
|
||||
ns.ownerid = ownerid
|
||||
ns.del_flg = '0'
|
||||
refer_cnt = 1
|
||||
await sor.C('media', ns.copy())
|
||||
|
||||
ftyp_f = {
|
||||
"jpg":save_image,
|
||||
"jpeg":save_image,
|
||||
"heic":save_image,
|
||||
"gif":save_image,
|
||||
"png":save_image,
|
||||
"cr2":save_image,
|
||||
"mkv":save_video,
|
||||
"flv":save_video,
|
||||
"webm":save_video,
|
||||
"avi":save_video,
|
||||
"mov":save_video,
|
||||
"mp4":save_video,
|
||||
"wmv":save_video,
|
||||
"mp3":save_audio,
|
||||
"aiff":save_audio,
|
||||
"ape":save_audio,
|
||||
"wav":save_audio,
|
||||
"flac":save_audio,
|
||||
"aac":save_audio,
|
||||
"ogg":save_audio,
|
||||
"wma":save_audio,
|
||||
"alac":save_audio,
|
||||
}
|
||||
async def save_media(f, webpath, userid):
|
||||
fs = FileStorage()
|
||||
db = DBPools()
|
||||
async with db.sqlorContext('mediadb') as sor:
|
||||
if f is None:
|
||||
f = fs.realPath(webpath)
|
||||
recs = await sor.R('media', {'mlocation':webpath, 'mloctype':'0'})
|
||||
if len(recs) > 0:
|
||||
debug(f'{f} already exists')
|
||||
return
|
||||
|
||||
sub = f.split('.')[-1].lower()
|
||||
func = ftyp_f.get(sub)
|
||||
if func:
|
||||
try:
|
||||
r = await func(sor, f, webpath, userid)
|
||||
debug(f'{f} loaded ...')
|
||||
return True
|
||||
except Exception as e:
|
||||
x = format_exc()
|
||||
debug(f'{f} is not loaded{e}\n{x}')
|
||||
else:
|
||||
debug(f'{f} is not loaded')
|
||||
return False
|
||||
|
||||
async def load_media(folder, username=None):
|
||||
folder = os.path.abspath(folder)
|
||||
debug(f'handle {folder} ...')
|
||||
fs = FileStorage()
|
||||
if not folder.startswith(fs.root):
|
||||
debug(f'{folder=} must start with {fs.root}')
|
||||
raise Exception(f'{folder=} must start with {fs.root}')
|
||||
userid = None
|
||||
if username:
|
||||
db = DBPools()
|
||||
async with db.sqlorContext('mediadb') as sor:
|
||||
users = await sor.R('users', {'username':username})
|
||||
if len(users) > 0:
|
||||
userid = users[0].id
|
||||
|
||||
for f in listFile(folder, rescursive=True):
|
||||
webpath = fs.webpath(f)
|
||||
await save_media(f, webpath, userid)
|
||||
|
||||
if __name__ == '__main__':
|
||||
async def main():
|
||||
parser = argparse.ArgumentParser(prog=sys.argv[0])
|
||||
parser.add_argument('-w', '--workdir')
|
||||
parser.add_argument('-u', '--username')
|
||||
parser.add_argument('folders', nargs='*')
|
||||
args = parser.parse_args()
|
||||
workdir = args.workdir or os.getcwd()
|
||||
config = getConfig(workdir, {'workdir':workdir})
|
||||
DBPools(config.databases)
|
||||
for folder in args.folders:
|
||||
await load_media(folder, username=args.username or 'ymq')
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
loop.run_until_complete(main())
|
||||
|
43
app/media.py
Normal file
@ -0,0 +1,43 @@
|
||||
import os
|
||||
from ahserver.webapp import webapp
|
||||
from ahserver.filestorage import FileStorage
|
||||
from ahserver.serverenv import ServerEnv
|
||||
from appPublic.registerfunction import RegisterFunction
|
||||
from appPublic.log import debug
|
||||
from appPublic.worker import awaitify
|
||||
|
||||
from appbase.init import load_appbase
|
||||
from rbac.init import load_rbac
|
||||
from findperson.init import load_findperson
|
||||
from ext import init_ui_ext
|
||||
from loadmedia import save_media, load_media
|
||||
from imageface import create_knownface
|
||||
|
||||
def runytdlp(cookie_webpath, url):
|
||||
# we have a dl in ~/bin, and ~bin in PATH
|
||||
fs = FileStorage()
|
||||
cookiefile = fs.realPath(cookie_webpath)
|
||||
cmd = f"""cd /d/ymq/media/dl; yt-dlp --cookies {cookiefile} --proxy socks5://127.0.0.1:1086 -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a] /best[ext=mp4]/best' "{url}" """
|
||||
debug(f'execute:{cmd}')
|
||||
os.system(cmd)
|
||||
|
||||
def get_module_dbname(mn):
|
||||
return 'mediadb'
|
||||
|
||||
def init():
|
||||
g = ServerEnv()
|
||||
g.get_module_dbname = get_module_dbname
|
||||
g.runytdlp = awaitify(runytdlp)
|
||||
g.create_knownface = create_knownface
|
||||
g.load_media = load_media
|
||||
g.save_media = save_media
|
||||
rf = RegisterFunction()
|
||||
rf.register('get_module_dbname', get_module_dbname)
|
||||
init_ui_ext()
|
||||
load_appbase()
|
||||
load_rbac()
|
||||
load_findperson()
|
||||
|
||||
if __name__ == '__main__':
|
||||
webapp(init)
|
||||
|
33
app/t.py
Normal file
@ -0,0 +1,33 @@
|
||||
import asyncio
|
||||
from sqlor.dbpools import DBPools
|
||||
from appPublic.jsonConfig import getConfig
|
||||
from ahserver.filestorage import FileStorage
|
||||
from imageinfo import get_image_info
|
||||
|
||||
async def main():
|
||||
db = DBPools()
|
||||
async with db.sqlorContext('mediadb') as sor:
|
||||
sql = "select a.* from media a left join mediainfo b on a.id = b.id where b.id is null and a.mtype = 'image'"
|
||||
recs = await sor.sqlExe(sql, {})
|
||||
fs = FileStorage()
|
||||
for r in recs:
|
||||
fp = fs.realPath(r.mlocation)
|
||||
print(fp, '...')
|
||||
lat, lon, ts = get_image_info(fp)
|
||||
info_status = '0'
|
||||
if ts:
|
||||
info_status = '1'
|
||||
ns = {
|
||||
"id":r.id,
|
||||
"info_status":info_status,
|
||||
"latitude":lat,
|
||||
"longitude":lon,
|
||||
"media_timestamp":ts
|
||||
}
|
||||
await sor.C('mediainfo', ns)
|
||||
|
||||
if __name__ == '__main__':
|
||||
config = getConfig('..', {'workdir':'..'})
|
||||
DBPools(config.databases)
|
||||
asyncio.new_event_loop().run_until_complete(main())
|
||||
|
61
app/videokeyframe.py
Normal file
@ -0,0 +1,61 @@
|
||||
import asyncio
|
||||
import os, sys
|
||||
import argparse
|
||||
import json
|
||||
|
||||
from sqlor.dbpools import DBPools
|
||||
from appPublic.jsonConfig import getConfig
|
||||
from appPublic.log import debug
|
||||
from appPublic.dictObject import DictObject
|
||||
from appPublic.timeUtils import curDateString, timestampstr
|
||||
from appPublic.uniqueID import getID
|
||||
from appPublic.folderUtils import listFile, _mkdir
|
||||
from ahserver.filestorage import FileStorage
|
||||
|
||||
import av
|
||||
|
||||
def get_1st_keyframe(mloc, loctype, userid):
|
||||
# Open the video file
|
||||
fs = FileStorage()
|
||||
if loctype == '0': # webpath
|
||||
mloc1 = fs.realPath(mloc)
|
||||
debug(f'{mloc=}, {mloc1=}')
|
||||
mloc = mloc1
|
||||
container = av.open(mloc)
|
||||
# Iterate through the video to find the first key frame
|
||||
kcnt = 0
|
||||
for frame in container.decode(video=0):
|
||||
if frame.key_frame:
|
||||
kcnt += 1
|
||||
if kcnt > 10:
|
||||
# Save the key frame as an image
|
||||
name = os.path.basename(mloc).split('.')[0] + '.jpg'
|
||||
fn = fs._name2path(name, userid=userid)
|
||||
webpath = fs.webpath(fn)
|
||||
_mkdir(os.path.dirname(fn))
|
||||
frame.to_image().save(fn)
|
||||
return webpath
|
||||
|
||||
async def gen_video_keyframe():
|
||||
db = DBPools()
|
||||
async with db.sqlorContext('mediadb') as sor:
|
||||
sql = "select * from media where mtype = 'video' and logo_url is null and mloctype in ('0', '1')"
|
||||
recs = await sor.sqlExe(sql, {})
|
||||
for r in recs:
|
||||
wp = get_1st_keyframe(r.mlocation, r.mloctype, r.ownerid)
|
||||
ns = {'id':r.id, 'logo_url':wp}
|
||||
await sor.U('media', ns)
|
||||
|
||||
async def main():
|
||||
parser = argparse.ArgumentParser(prog=sys.argv[0])
|
||||
parser.add_argument('-w', '--workdir')
|
||||
args = parser.parse_args()
|
||||
workdir = args.workdir or os.getcwd()
|
||||
config = getConfig(workdir, {'workdir':workdir})
|
||||
DBPools(config.databases)
|
||||
await gen_video_keyframe()
|
||||
|
||||
if __name__ == '__main__':
|
||||
loop = asyncio.new_event_loop()
|
||||
loop.run_until_complete(main())
|
||||
|
2
appbase_roleperm.sh
Executable file
@ -0,0 +1,2 @@
|
||||
#!/usr/bin/bash
|
||||
python ~/py/rbac/script/roleperm.py mediadb rbac owner superuser appcodes appcodes_kv params
|
83
conf/config.json
Executable file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"logger":{
|
||||
"name":"media",
|
||||
"levelname":"debug",
|
||||
"logfile":"$[workdir]$/logs/media.log"
|
||||
},
|
||||
"filesroot":"/d/ymq/media",
|
||||
"vectordb_path":"$[workdir]$/vdb/media.db",
|
||||
"databases":{
|
||||
"mediadb":{
|
||||
"driver":"aiomysql",
|
||||
"async_mode":true,
|
||||
"coding":"utf8",
|
||||
"dbname":"mediadb",
|
||||
"maxconn":100,
|
||||
"kwargs":{
|
||||
"user":"test",
|
||||
"db":"mediadb",
|
||||
"password":"QUZVcXg5V1p1STMybG5Ia6mX9D0v7+g=",
|
||||
"host":"localhost"
|
||||
}
|
||||
}
|
||||
},
|
||||
"website":{
|
||||
"paths":[
|
||||
["$[workdir]$/wwwroot",""]
|
||||
],
|
||||
"client_max_size":10000,
|
||||
"host":"0.0.0.0",
|
||||
"port":10182,
|
||||
"coding":"utf-8",
|
||||
"ssl_gg":{
|
||||
"crtfile":"$[workdir]$/conf/www.bsppo.com.pem",
|
||||
"keyfile":"$[workdir]$/conf/www.bsppo.com.key"
|
||||
},
|
||||
"indexes":[
|
||||
"index.html",
|
||||
"index.tmpl",
|
||||
"index.ui",
|
||||
"index.dspy",
|
||||
"index.md"
|
||||
],
|
||||
"processors":[
|
||||
[".ws","ws"],
|
||||
[".xterm","xterm"],
|
||||
[".proxy","proxy"],
|
||||
[".llm", "llm"],
|
||||
[".xlsxds","xlsxds"],
|
||||
[".sqlds","sqlds"],
|
||||
[".tmpl.js","tmpl"],
|
||||
[".tmpl.css","tmpl"],
|
||||
[".html.tmpl","tmpl"],
|
||||
[".bcrud", "bricks_crud"],
|
||||
[".tmpl","tmpl"],
|
||||
[".bui","bui"],
|
||||
[".ui","bui"],
|
||||
[".dspy","dspy"],
|
||||
[".md","md"]
|
||||
],
|
||||
"startswiths":[
|
||||
{
|
||||
"leading":"/idfile",
|
||||
"registerfunction":"idfile"
|
||||
}
|
||||
],
|
||||
|
||||
"rsakey":{
|
||||
"privatekey":"$[workdir]$/conf/rsa_private_key.pem",
|
||||
"publickey":"$[workdir]$/conf/rsa_public_key.pem"
|
||||
},
|
||||
"session_max_time":3000,
|
||||
"session_issue_time":2500,
|
||||
"session_redis":{
|
||||
"url":"redis://127.0.0.1:6379"
|
||||
}
|
||||
},
|
||||
"langMapping":{
|
||||
"zh-Hans-CN":"zh-cn",
|
||||
"zh-CN":"zh-cn",
|
||||
"en-us":"en",
|
||||
"en-US":"en"
|
||||
}
|
||||
}
|
24
json/media.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"tblname":"media",
|
||||
"aliasoops":"video",
|
||||
"params":{
|
||||
"toolbar":{
|
||||
"tools":[
|
||||
{
|
||||
"name":"play",
|
||||
"label":"play",
|
||||
"url":"{{entire_url('../ext/play_video.ui')}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sortby":"name",
|
||||
"browserfields": {
|
||||
"exclouded": ["id" ],
|
||||
"cwidth": {}
|
||||
},
|
||||
"logined_userid":"ownerid",
|
||||
"editexclouded": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
}
|
BIN
models/imageface.xlsx
Normal file
BIN
models/imageinfo.xlsx
Normal file
BIN
models/knownface.xlsx
Normal file
BIN
models/media.xlsx
Normal file
BIN
models/mediainfo.xlsx
Normal file
47
models/mysql.ddl.sql
Normal file
@ -0,0 +1,47 @@
|
||||
drop table if exists imageinfo;
|
||||
CREATE TABLE imageinfo
|
||||
(
|
||||
|
||||
`id` VARCHAR(32) comment 'id',
|
||||
`info_status` VARCHAR(1) comment '信息状态',
|
||||
`latitude` double(16,8) comment '纬度',
|
||||
`longitude` double(16,8) comment '经度',
|
||||
`media_timestamp` TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '时间戳',
|
||||
`face_cnt` int comment '脸数'
|
||||
|
||||
|
||||
,primary key(id)
|
||||
|
||||
|
||||
)
|
||||
engine=innodb
|
||||
default charset=utf8
|
||||
comment '图片信息'
|
||||
;
|
||||
|
||||
|
||||
-- ./imageface.xlsx
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
drop table if exists imageface;
|
||||
CREATE TABLE imageface
|
||||
(
|
||||
|
||||
`id` VARCHAR(32) comment 'id',
|
||||
`imageid` VARCHAR(32) comment 'imageid',
|
||||
`faceid` VARCHAR(32) comment 'faceid'
|
||||
|
||||
|
||||
,primary key(id)
|
||||
|
||||
|
||||
)
|
||||
engine=innodb
|
||||
default charset=utf8
|
||||
comment '图片人脸'
|
||||
;
|
||||
|
||||
|
7
rbac_roleperm.sh
Normal file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
python ~/py/rbac/script/roleperm.py mediadb rbac owner superuser role permission rolepermission user user role organization orgtypes
|
||||
python ~/py/rbac/script/roleperm.py mediadb rbac owner admin user userrole
|
||||
python ~/py/rbac/script/roleperm.py mediadb rbac customer admin user userrole
|
||||
# python ~/py/rbac/script/roleperm.py mediadb rbac reseller admin user userrole
|
||||
|
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
fingerprint
|
||||
pillow
|
||||
pyheif
|
||||
exifread
|
||||
pillow_heif
|
98
script/media.nginx
Normal file
@ -0,0 +1,98 @@
|
||||
server {
|
||||
autoindex on;
|
||||
client_max_body_size 20g;
|
||||
server_name kymoz.com;
|
||||
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-server $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Scheme $scheme;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header X-Forwarded-Url "$scheme://$host:$server_port$request_uri";
|
||||
|
||||
index index.html index.htm;
|
||||
|
||||
location ~^/ip$ {
|
||||
return 200 "$remote_addr";
|
||||
}
|
||||
|
||||
location ^~ /wss/ {
|
||||
proxy_pass http://localhost:10183/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Prepath "wss";
|
||||
}
|
||||
location ^~ /msp\/wss/ {
|
||||
proxy_pass http://localhost:10101/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Prepath "msp/wss";
|
||||
}
|
||||
location ^~ /msp/ {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-server $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Scheme $scheme;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header X-real-ip $remote_addr;
|
||||
proxy_send_timeout 600s;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_set_header X-Forwarded-Prepath 'msp';
|
||||
proxy_pass http://localhost:10100/;
|
||||
}
|
||||
location ^~ /tv/ {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-server $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Scheme $scheme;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header X-real-ip $remote_addr;
|
||||
proxy_send_timeout 600s;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_set_header X-Forwarded-Prepath 'tv';
|
||||
proxy_pass http://localhost:10180/;
|
||||
}
|
||||
location / {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-server $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Scheme $scheme;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
proxy_set_header X-real-ip $remote_addr;
|
||||
proxy_send_timeout 600s;
|
||||
proxy_read_timeout 600s;
|
||||
proxy_pass http://localhost:10182/;
|
||||
}
|
||||
|
||||
listen 10443 ssl; # managed by Certbot
|
||||
ssl_certificate /etc/letsencrypt/live/kymoz.com/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/kymoz.com/privkey.pem; # managed by Certbot
|
||||
}
|
||||
server {
|
||||
if ($host = kymoz.com) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
|
||||
listen 10080;
|
||||
server_name kymoz.com;
|
||||
return 404; # managed by Certbot
|
||||
|
||||
|
||||
}
|
16
script/media.service
Normal file
@ -0,0 +1,16 @@
|
||||
[Unit]
|
||||
Description=media service
|
||||
Documention=media service to control media service start or stoop
|
||||
After=mariadb.service
|
||||
Wants=systemd-networkd.service
|
||||
Requires=nginx.service
|
||||
|
||||
[Service]
|
||||
User=ymq
|
||||
Group=ymq
|
||||
Type=forking
|
||||
ExecStart="/home/ymq/py/media/script/run.sh"
|
||||
ExecStop="/home/ymq/py/media/script/stop.sh"
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
5
script/run.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
$HOME/py3/bin/python $HOME/py/media/app/media.py -w $HOME/py/media >$HOME/py/media/logs/stderr.log 2>&1 &
|
||||
$HOME/py3/bin/python $HOME/py/media/app/media.py -w $HOME/py/media -p 10183 >>$HOME/py/media/logs/stderr.log 2>&1 &
|
||||
exit 0
|
3
script/stop.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bin
|
||||
|
||||
/home/ymq/bin/killname app/media.py
|
42
wwwroot/app_panel.ui
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"widgettype":"HBox",
|
||||
"options":{
|
||||
"css":"filler"
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Image",
|
||||
"options":{
|
||||
"height":"100%",
|
||||
"css":"clickable",
|
||||
"url":"{{entire_url('/imgs/media.png')}}"
|
||||
},
|
||||
"binds":[
|
||||
{
|
||||
"wid":"self",
|
||||
"event":"click",
|
||||
"actiontype":"urlwidget",
|
||||
"target":"Popup",
|
||||
"popup_options":{
|
||||
"cwidth":10,
|
||||
"eventpos":true,
|
||||
"dismiss_events":["command"]
|
||||
},
|
||||
"options":{
|
||||
"url":"{{entire_url('menu.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype":"Title4",
|
||||
"options":{
|
||||
"i18n":true,
|
||||
"otext":"媒体平台",
|
||||
"wrap":true,
|
||||
"halign":"left"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
1
wwwroot/appbase
Symbolic link
@ -0,0 +1 @@
|
||||
/home/ymq/py/appbase/wwwroot
|
46
wwwroot/audio/audiolist.ui
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"id":"audiolist",
|
||||
"widgettype":"Cols",
|
||||
"options":{
|
||||
"css":"filler",
|
||||
"data_url":"{{entire_url('get_audios.dspy')}}",
|
||||
"data_params":{
|
||||
},
|
||||
"mobile_cols":2,
|
||||
"col_cwidth":16,
|
||||
"col_cheight":12,
|
||||
"record_view":{
|
||||
"widgettype":"VBox",
|
||||
"options":{
|
||||
"css":"card"
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Title4",
|
||||
"options":{
|
||||
"text":"${name}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"binds":[
|
||||
{
|
||||
"wid":"self",
|
||||
"event":"click",
|
||||
"actiontype":"urlwidget",
|
||||
"target":"PopupWindow",
|
||||
"popup_options":{
|
||||
"width":"80%",
|
||||
"height":"80%"
|
||||
},
|
||||
"options":{
|
||||
"url":"{{entire_url('audioplay.dspy')}}",
|
||||
"params":{
|
||||
"id":"${id}"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
wwwroot/audio/audioplay.dspy
Normal file
@ -0,0 +1,21 @@
|
||||
ns = {
|
||||
'id':params_kw.id
|
||||
}
|
||||
|
||||
db = DBPools()
|
||||
async with db.sqlorContext('mediadb') as sor:
|
||||
recs = await sor.R('media', ns)
|
||||
if len(recs) >= 1:
|
||||
r = recs[0]
|
||||
mtype = r.mlocation.rsplit('.',1)[-1]
|
||||
typ = f'audio/{mtype}'
|
||||
return {
|
||||
"widgettype":"AudioPlayer",
|
||||
"id":"player",
|
||||
"options":{
|
||||
"height":"99%",
|
||||
"width":"30%",
|
||||
"url":entire_url('/idfile') + "?path=" + r.mlocation,
|
||||
"autoplay":True
|
||||
}
|
||||
}
|
34
wwwroot/audio/get_audios.dspy
Normal file
@ -0,0 +1,34 @@
|
||||
ns = {
|
||||
'page':params_kw.get('page', 1),
|
||||
'sort':'name',
|
||||
'rowpages':80
|
||||
}
|
||||
userid = await get_user()
|
||||
if not userid:
|
||||
return {
|
||||
"widgettype":"Error",
|
||||
"options":{
|
||||
"title":"Authorization Error",
|
||||
"timeout":3,
|
||||
"cwidth":16,
|
||||
"cheight":9,
|
||||
"message":"Please login"
|
||||
}
|
||||
}
|
||||
ns['ownerid'] = userid
|
||||
sql = """select * from media where mtype = 'audio' and (ownerid=${ownerid}$ or ownerid is NULL)"""
|
||||
if params_kw.search:
|
||||
b = params_kw.search.split(' ')
|
||||
b.insert(0, '')
|
||||
b.append('')
|
||||
loc = '%'.join(b)
|
||||
ns['loc'] = loc
|
||||
sql += " and mlocation like ${loc}$"
|
||||
db = DBPools()
|
||||
async with db.sqlorContext('mediadb') as sor:
|
||||
r = await sor.sqlPaging(sql, ns.copy())
|
||||
return r
|
||||
return {
|
||||
'total':0,
|
||||
'rows':[]
|
||||
}
|
37
wwwroot/audio/index.ui
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"widgettype":"VBox",
|
||||
"options":{
|
||||
"height":"100%",
|
||||
"width":"100%"
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Title4",
|
||||
"options":{
|
||||
"i18n":true,
|
||||
"otext":"音频"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype":"urlwidget",
|
||||
"options":{
|
||||
"url":"{{entire_url('search.ui')}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype":"urlwidget",
|
||||
"options":{
|
||||
"url":"{{entire_url('audiolist.ui')}}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"binds":[
|
||||
{
|
||||
"wid":"search",
|
||||
"event":"changed",
|
||||
"actiontype":"script",
|
||||
"target":"audiolist",
|
||||
"script":"console.log(this, params); this.load_first_page(params)"
|
||||
}
|
||||
]
|
||||
}
|
32
wwwroot/audio/search.ui
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"widgettype":"HBox",
|
||||
"options":{
|
||||
"cheight":2,
|
||||
"width":"100%",
|
||||
"dynsiz":true
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Text",
|
||||
"options":{
|
||||
"width":"auto",
|
||||
"otext":"搜索",
|
||||
"i18n":true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype":"Filler",
|
||||
"options":{},
|
||||
"subwidgets":[
|
||||
{
|
||||
"id":"search",
|
||||
"widgettype":"UiStr",
|
||||
"options":{
|
||||
"name":"search",
|
||||
"width":"100%"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
17
wwwroot/bottom.ui
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"widgettype":"HBox",
|
||||
"options":{
|
||||
"cheight":2,
|
||||
"bgcolor":"#e5e5e5"
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Text",
|
||||
"options":{
|
||||
"otext":"© 2024 版权所有",
|
||||
"i18n":true,
|
||||
"wrap":true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
1
wwwroot/bricks
Symbolic link
@ -0,0 +1 @@
|
||||
/tmp/dist
|
15
wwwroot/center.ui
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"widgettype":"VBox",
|
||||
"id":"page_center",
|
||||
"options":{
|
||||
"css":"filler"
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"urlwidget",
|
||||
"options":{
|
||||
"url":"{{entire_url('public')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
4
wwwroot/download_media.dspy
Normal file
@ -0,0 +1,4 @@
|
||||
debug(f'{params_kw=}')
|
||||
userid = await get_user()
|
||||
await runytdlp(params_kw.cookiepath, params_kw.url)
|
||||
await load_media('/d/ymq/media/dl', userid)
|
33
wwwroot/download_media.ui
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"id":"download",
|
||||
"widgettype":"Form",
|
||||
"options":{
|
||||
"title":"从网上下载媒体",
|
||||
"description":"需要将网站的cookie,和响应媒体的url上传过来",
|
||||
"fields":[
|
||||
{
|
||||
"name":"cookiepath",
|
||||
"label":"网站cookie",
|
||||
"uitype":"file",
|
||||
"required":true
|
||||
},
|
||||
{
|
||||
"name":"url",
|
||||
"label":"媒体页url",
|
||||
"uitype":"str",
|
||||
"required":true
|
||||
}
|
||||
]
|
||||
},
|
||||
"binds":[
|
||||
{
|
||||
"wid":"self",
|
||||
"event":"submit",
|
||||
"actiontype":"urlwidget",
|
||||
"target":"PopupWindow",
|
||||
"options":{
|
||||
"url":"{{entire_url('download_media.dspy')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
1
wwwroot/enpwd.dspy
Normal file
@ -0,0 +1 @@
|
||||
return password_encode('111111')
|
6
wwwroot/findperson.dspy
Normal file
@ -0,0 +1,6 @@
|
||||
debug(f'{params_kw=}')
|
||||
fp = realpath(params_kw.mediafile)
|
||||
faces = await find_face_in_image(fp, 100)
|
||||
ids = [f.entity.imgid for f in faces]
|
||||
debug(f'{faces=}, {ids=}')
|
||||
await redirect(entire_url('/idsimages') + '?ids=' + ','.join(ids))
|
37
wwwroot/findperson.ui
Normal file
@ -0,0 +1,37 @@
|
||||
{% if get_user() %}
|
||||
{
|
||||
"id":"uplaod",
|
||||
"widgettype":"Form",
|
||||
"options":{
|
||||
"title":"上传照片",
|
||||
"description":"可以上传人物图片文件",
|
||||
"submit_url_oops":"{{entire_url('findperson.dspy')}}",
|
||||
"fields":[
|
||||
{
|
||||
"name":"mediafile",
|
||||
"uitype":"file",
|
||||
"required":true
|
||||
}
|
||||
]
|
||||
},
|
||||
"binds":[
|
||||
{
|
||||
"wid":"self",
|
||||
"event":"submit",
|
||||
"actiontype":"urlwidget",
|
||||
"target":"PopupWindow",
|
||||
"options":{
|
||||
"url":"{{entire_url('findperson.dspy')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
{% else %}
|
||||
{
|
||||
"widgettype":"Message",
|
||||
"options":{
|
||||
"title":"Error",
|
||||
"message":"You need login"
|
||||
}
|
||||
}
|
||||
{% endif %}
|
16
wwwroot/idsimages/get_images.dspy
Normal file
@ -0,0 +1,16 @@
|
||||
ns = {
|
||||
"ids": params_kw.ids.split(','),
|
||||
'page':params_kw.get('page', 1),
|
||||
'sort':'name',
|
||||
'rowpages':80
|
||||
}
|
||||
sql = """select * from media where id in ${ids}$"""
|
||||
debug(f'{sql=}, {ns=}')
|
||||
db = DBPools()
|
||||
async with db.sqlorContext('mediadb') as sor:
|
||||
r = await sor.sqlPaging(sql, ns.copy())
|
||||
return r
|
||||
return {
|
||||
'total':0,
|
||||
'rows':[]
|
||||
}
|
61
wwwroot/idsimages/imagelist.ui
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"id":"imagelist",
|
||||
"widgettype":"Cols",
|
||||
"options":{
|
||||
"css":"filler",
|
||||
"data_url":"{{entire_url('get_images.dspy')}}",
|
||||
"data_params":{
|
||||
"ids":"{{params_kw.ids}}"
|
||||
},
|
||||
"mobile_cols":2,
|
||||
"col_cwidth":16,
|
||||
"col_cheight":12,
|
||||
"record_view":{
|
||||
"widgettype":"VBox",
|
||||
"options":{
|
||||
"css":"card"
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Title4",
|
||||
"options":{
|
||||
"text":"${name}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype":"Filler",
|
||||
"options":{
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Image",
|
||||
"options":{
|
||||
"width":"100%",
|
||||
"url":"{{entire_url('/idfile')}}?path=${logo_url}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"binds":[
|
||||
{
|
||||
"wid":"self",
|
||||
"event":"click",
|
||||
"actiontype":"urlwidget",
|
||||
"target":"PopupWindow",
|
||||
"popup_options":{
|
||||
"width":"80%",
|
||||
"height":"80%"
|
||||
},
|
||||
"options":{
|
||||
"url":"{{entire_url('imageplay.dspy')}}",
|
||||
"params":{
|
||||
"id":"${id}"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
51
wwwroot/idsimages/imageplay.dspy
Normal file
@ -0,0 +1,51 @@
|
||||
ns = {
|
||||
'id':params_kw.id
|
||||
}
|
||||
|
||||
db = DBPools()
|
||||
async with db.sqlorContext('mediadb') as sor:
|
||||
recs = await sor.R('media', ns)
|
||||
if len(recs) >= 1:
|
||||
r = recs[0]
|
||||
return {
|
||||
"widgettype":"VBox",
|
||||
"options":{},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Filler",
|
||||
"options":{},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Image",
|
||||
"id":"player",
|
||||
"options":{
|
||||
"witdh":"100%",
|
||||
"height":"100%",
|
||||
"url":entire_url('/idfile') + "?path=" + r.mlocation,
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":"iconbar",
|
||||
"widgettype":"IconBar",
|
||||
"options":{
|
||||
"cheight":2,
|
||||
"tools":[
|
||||
{
|
||||
"name":"download",
|
||||
"icon":entire_url('/imgs/download.png'),
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"binds":[
|
||||
{
|
||||
"wid":"iconbar",
|
||||
"event":"download",
|
||||
"actiontype":"script",
|
||||
"target":"self",
|
||||
"script":"open('" + entire_url('/idfile') + "?path=" + r.mlocation + "&download=1');"
|
||||
}]
|
||||
}
|
25
wwwroot/idsimages/index.ui
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"widgettype":"VBox",
|
||||
"options":{
|
||||
"height":"100%",
|
||||
"width":"100%"
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Title4",
|
||||
"options":{
|
||||
"i18n":true,
|
||||
"otext":"图片"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype":"urlwidget",
|
||||
"options":{
|
||||
"params":{
|
||||
"ids":"{{params_kw.ids}}"
|
||||
},
|
||||
"url":"{{entire_url('imagelist.ui')}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
32
wwwroot/idsimages/search.ui
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"widgettype":"HBox",
|
||||
"options":{
|
||||
"cheight":2,
|
||||
"width":"100%",
|
||||
"dynsiz":true
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Text",
|
||||
"options":{
|
||||
"width":"auto",
|
||||
"otext":"搜索",
|
||||
"i18n":true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype":"Filler",
|
||||
"options":{},
|
||||
"subwidgets":[
|
||||
{
|
||||
"id":"search",
|
||||
"widgettype":"UiStr",
|
||||
"options":{
|
||||
"name":"search",
|
||||
"width":"100%"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
32
wwwroot/idsimages/t.ui
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Filler",
|
||||
"options": {},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Image",
|
||||
"id": "player",
|
||||
"options": {
|
||||
"witdh": "100%",
|
||||
"height": "100%",
|
||||
"url": "https://kymoz.com:10443/idfile?path=/images/\u4f59\u9526\u6d53/100\u5929/002.JPG"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"binds": [
|
||||
{
|
||||
"wid": "iconbar",
|
||||
"event": "download",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "self",
|
||||
"options": {
|
||||
"url": "https://kymoz.com:10443/download?path=/images/\u4f59\u9526\u6d53/100\u5929/002.JPG"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
34
wwwroot/image/get_images.dspy
Normal file
@ -0,0 +1,34 @@
|
||||
ns = {
|
||||
'page':params_kw.get('page', 1),
|
||||
'sort':'name',
|
||||
'rowpages':80
|
||||
}
|
||||
userid = await get_user()
|
||||
if not userid:
|
||||
return {
|
||||
"widgettype":"Error",
|
||||
"options":{
|
||||
"title":"Authorization Error",
|
||||
"timeout":3,
|
||||
"cwidth":16,
|
||||
"cheight":9,
|
||||
"message":"Please login"
|
||||
}
|
||||
}
|
||||
ns['ownerid'] = userid
|
||||
sql = """select * from media where mtype = 'image' and (ownerid=${ownerid}$ or ownerid is NULL)"""
|
||||
if params_kw.search:
|
||||
b = params_kw.search.split(' ')
|
||||
b.insert(0, '')
|
||||
b.append('')
|
||||
loc = '%'.join(b)
|
||||
ns['loc'] = loc
|
||||
sql += " and mlocation like ${loc}$"
|
||||
db = DBPools()
|
||||
async with db.sqlorContext('mediadb') as sor:
|
||||
r = await sor.sqlPaging(sql, ns.copy())
|
||||
return r
|
||||
return {
|
||||
'total':0,
|
||||
'rows':[]
|
||||
}
|
60
wwwroot/image/imagelist.ui
Normal file
@ -0,0 +1,60 @@
|
||||
{
|
||||
"id":"imagelist",
|
||||
"widgettype":"Cols",
|
||||
"options":{
|
||||
"css":"filler",
|
||||
"data_url":"{{entire_url('get_images.dspy')}}",
|
||||
"data_params":{
|
||||
},
|
||||
"mobile_cols":2,
|
||||
"col_cwidth":16,
|
||||
"col_cheight":12,
|
||||
"record_view":{
|
||||
"widgettype":"VBox",
|
||||
"options":{
|
||||
"css":"card"
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Title4",
|
||||
"options":{
|
||||
"text":"${name}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype":"Filler",
|
||||
"options":{
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Image",
|
||||
"options":{
|
||||
"width":"100%",
|
||||
"url":"{{entire_url('/idfile')}}?path=${logo_url}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"binds":[
|
||||
{
|
||||
"wid":"self",
|
||||
"event":"click",
|
||||
"actiontype":"urlwidget",
|
||||
"target":"PopupWindow",
|
||||
"popup_options":{
|
||||
"width":"80%",
|
||||
"height":"80%"
|
||||
},
|
||||
"options":{
|
||||
"url":"{{entire_url('imageplay.dspy')}}",
|
||||
"params":{
|
||||
"id":"${id}"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
51
wwwroot/image/imageplay.dspy
Normal file
@ -0,0 +1,51 @@
|
||||
ns = {
|
||||
'id':params_kw.id
|
||||
}
|
||||
|
||||
db = DBPools()
|
||||
async with db.sqlorContext('mediadb') as sor:
|
||||
recs = await sor.R('media', ns)
|
||||
if len(recs) >= 1:
|
||||
r = recs[0]
|
||||
return {
|
||||
"widgettype":"VBox",
|
||||
"options":{},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Filler",
|
||||
"options":{},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Image",
|
||||
"id":"player",
|
||||
"options":{
|
||||
"witdh":"100%",
|
||||
"height":"100%",
|
||||
"url":entire_url('/idfile') + "?path=" + r.mlocation,
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":"iconbar",
|
||||
"widgettype":"IconBar",
|
||||
"options":{
|
||||
"cheight":2,
|
||||
"tools":[
|
||||
{
|
||||
"name":"download",
|
||||
"icon":entire_url('/imgs/download.png'),
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"binds":[
|
||||
{
|
||||
"wid":"iconbar",
|
||||
"event":"download",
|
||||
"actiontype":"script",
|
||||
"target":"self",
|
||||
"script":"open('" + entire_url('/idfile') + "?path=" + r.mlocation + "&download=1');"
|
||||
}]
|
||||
}
|
37
wwwroot/image/index.ui
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"widgettype":"VBox",
|
||||
"options":{
|
||||
"height":"100%",
|
||||
"width":"100%"
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Title4",
|
||||
"options":{
|
||||
"i18n":true,
|
||||
"otext":"图片"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype":"urlwidget",
|
||||
"options":{
|
||||
"url":"{{entire_url('search.ui')}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype":"urlwidget",
|
||||
"options":{
|
||||
"url":"{{entire_url('imagelist.ui')}}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"binds":[
|
||||
{
|
||||
"wid":"search",
|
||||
"event":"changed",
|
||||
"actiontype":"script",
|
||||
"target":"imagelist",
|
||||
"script":"console.log(this, params); this.load_first_page(params)"
|
||||
}
|
||||
]
|
||||
}
|
32
wwwroot/image/search.ui
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"widgettype":"HBox",
|
||||
"options":{
|
||||
"cheight":2,
|
||||
"width":"100%",
|
||||
"dynsiz":true
|
||||
},
|
||||
"subwidgets":[
|
||||
{
|
||||
"widgettype":"Text",
|
||||
"options":{
|
||||
"width":"auto",
|
||||
"otext":"搜索",
|
||||
"i18n":true
|
||||
}
|
||||
},
|
||||
{
|
||||
"widgettype":"Filler",
|
||||
"options":{},
|
||||
"subwidgets":[
|
||||
{
|
||||
"id":"search",
|
||||
"widgettype":"UiStr",
|
||||
"options":{
|
||||
"name":"search",
|
||||
"width":"100%"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
32
wwwroot/image/t.ui
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"widgettype": "VBox",
|
||||
"options": {},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Filler",
|
||||
"options": {},
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype": "Image",
|
||||
"id": "player",
|
||||
"options": {
|
||||
"witdh": "100%",
|
||||
"height": "100%",
|
||||
"url": "https://kymoz.com:10443/idfile?path=/images/\u4f59\u9526\u6d53/100\u5929/002.JPG"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"binds": [
|
||||
{
|
||||
"wid": "iconbar",
|
||||
"event": "download",
|
||||
"actiontype": "urlwidget",
|
||||
"target": "self",
|
||||
"options": {
|
||||
"url": "https://kymoz.com:10443/download?path=/images/\u4f59\u9526\u6d53/100\u5929/002.JPG"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
BIN
wwwroot/imgs/baichuan.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
wwwroot/imgs/bc_black.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
wwwroot/imgs/bc_empty.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
wwwroot/imgs/bc_white.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
wwwroot/imgs/bl_black.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
wwwroot/imgs/bl_empty.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
wwwroot/imgs/bl_white.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
wwwroot/imgs/br_black.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
wwwroot/imgs/br_empty.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
wwwroot/imgs/br_white.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
wwwroot/imgs/chatgpt.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
wwwroot/imgs/cl_black.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
wwwroot/imgs/cl_empty.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
wwwroot/imgs/cl_white.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
wwwroot/imgs/cr_black.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
wwwroot/imgs/cr_empty.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
wwwroot/imgs/cr_white.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
wwwroot/imgs/deepseek.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
wwwroot/imgs/dot_empty.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
wwwroot/imgs/doubao.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
wwwroot/imgs/download.png
Normal file
After Width: | Height: | Size: 9.9 KiB |
BIN
wwwroot/imgs/ii_black.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
wwwroot/imgs/ii_empty.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
wwwroot/imgs/ii_white.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
wwwroot/imgs/key.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
wwwroot/imgs/like.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
wwwroot/imgs/login.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
wwwroot/imgs/logout.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
wwwroot/imgs/media.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
wwwroot/imgs/minimax.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
wwwroot/imgs/moonshot.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
wwwroot/imgs/ollama.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
wwwroot/imgs/people.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
wwwroot/imgs/play.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
wwwroot/imgs/qianfan.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
wwwroot/imgs/qianwen.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
wwwroot/imgs/register.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
wwwroot/imgs/sage.png
Normal file
After Width: | Height: | Size: 1.0 MiB |
BIN
wwwroot/imgs/search.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
wwwroot/imgs/sensetime.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
wwwroot/imgs/submit.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
wwwroot/imgs/tc_black.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
wwwroot/imgs/tc_empty.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
wwwroot/imgs/tc_white.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
wwwroot/imgs/tl_black.png
Normal file
After Width: | Height: | Size: 9.2 KiB |
BIN
wwwroot/imgs/tl_empty.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
wwwroot/imgs/tl_white.png
Normal file
After Width: | Height: | Size: 9.2 KiB |