欢迎光临UUpython
最大、最新、最全的Python代码收集站

[GUI]tkinter批量压缩图片

uupython阅读(426)

这段代码是一个基于 tkinter 的图形界面程序,用于实现一个照片压缩子系统。该程序提供了一个主页面和一个子系统页面,用户可以选择照片文件夹并对文件夹内的照片进行批量压缩。

以下是代码的主要部分:

  1. MyApp 类:继承自 tk.Tk,用于创建应用程序的主窗口。主要创建了一个 ttk.Notebook,用于切换主页面和子系统页面。
  2. Page 类:用于创建页面的基类。每个页面都继承自这个类,包含一个框架,用于放置小部件。
  3. StartPage 类:继承自 Page 类,用于创建主页面。在主页面上显示一个欢迎标签。
  4. Page2 类:继承自 Page 类,用于创建子系统2页面。该页面包含了选择文件夹、压缩照片、重置等功能。利用 PIL 库对图片进行压缩,并在文本框中显示操作信息。
  5. yasuo 方法:用于压缩照片,遍历文件夹中的图片文件,对图片进行压缩,并在进度条和文本框中显示操作信息。
  6. open_folder 方法:用于选择文件夹路径。
  7. set_entry 方法:用于清空文件夹路径和文本框内容。
  8. return_to_start 方法:用于返回主页面。
  9. if __name__ == "__main__":创建 MyApp 实例并运行主循环。

请注意,这是一个简化的示例代码,其中可能会缺少某些错误处理和细节。为了让程序更加健壮和完善,你可能需要添加一些错误处理、用户交互提示以及更详细的注释。

import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
import tkinter.messagebox
from tkinter import scrolledtext#滚动文本框组件
from tkinter import Menu
from tkinter import ttk
import time
import re
import os
import os.path as osp
from PIL import Image#处理图片库
 
class MyApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("My App")
        self.geometry("1080x680+390+190")
        #self.resizable(0, 0)
        # 创建一个欢迎Label
        tk.Label(self, text='`欢迎进入百胜通信工程有限公司工作系统`', font=("楷体", 15)).pack(padx=20, pady=5)
        # 添加分隔线
        ttk.Separator(self, orient="horizontal").pack(fill="x")
        ttk.Separator(self, orient="horizontal").pack(fill="x")        
        # 创建Notebook小部件
        self.notebook = ttk.Notebook(self)
        self.notebook.pack(fill="both", expand=True)
                 
        # 创建页面列表,定义了一个名为pages的元组列表,其中每个元组包含页面的名称和实现页面的类
        pages = [
            ("主页面", StartPage),
            ("子系统2", Page2),
 
         
        ]
        # 使用循环创建页面
        for name, cls in pages:
            page = cls(self.notebook)
            self.notebook.add(page, text=name)
         
        # 修改选项卡文字字体
        style = ttk.Style()
        style.configure("TNotebook.Tab", font=("楷体", 10), foreground="red")
 
 
# 创建了一个名为Page的基类
class Page(ttk.Frame):
    def __init__(self, parent, name):
        super().__init__(parent)
        self.name = name
        # 添加分隔线
        ttk.Separator(self, orient="horizontal").pack(fill='x')
        ttk.Separator(self, orient="horizontal").pack(fill='x')
        # 创建一个Frame框架
        self.frame = tk.Frame(self, width=800, height=850, highlightbackground="black", highlightthickness=1, bd=3)
        self.frame.pack(side='left', fill='y', padx=135, pady=10)  # side设置left从左至右排列,fill设置y垂直方向填充空白方式
        #添加到父容器中
        self.pack(fill=tk.BOTH, expand=True)  # self.master.pack()就会卡死
# 创建从Page基类派生出每个页面的子类。每个子类都包含该页面的所有小部件和方法
class StartPage(Page):
    def __init__(self, parent):
        super().__init__(parent, "Start_Page")
        tk.Label(self.frame, text="这是主页面").place(relx=0.5, rely=0.0, anchor='n')
 
class Page2(Page):
    def __init__(self, parent):
        super().__init__(parent, "子系统2")
        self.pic=[]#照片文件夹列表
        self.dic={}#存放文件夹名和文件夹内文件名的键值对
        self.createWidget()
    def createWidget(self):
        tk.Label(self.frame, text="**********照片压缩子系统**********", width=40, height=2, font=("黑体", 15)).place(relx=0.5, rely=0.0, anchor='n')
        tk.Label(self.frame, text="目录路径:", bd=5, width=10, height=2, font=("楷体", 15)).place(x=142, y=100)
        self.lujing = tk.Entry(self.frame, width=25)
        self.lujing.place(x=238, y=116)
        # bind()方法将open_folder()函数与“<Return>”键绑定,以便在用户按下“Enter”键时自动打开所选的文件夹
        #self.lujing.bind("<Return>", self.open_folder)
        tk.Button(self.frame, text="选择", font=("楷体", 11), width=3, height=1,bg='#BFEFFA',
                   command=self.open_folder).place(x=420, y=113)
        tk.Button(self.frame, text="压缩", font=("楷体", 11), width=3, height=1,bg='#BFEFFA',
                   command=self.yasuo).place(x=465, y=113)
        tk.Button(self.frame, text="重置", font=("楷体", 11), width=3, height=1,bg='#BFEFFA',
                   command=self.set_entry).place(x=300, y=480)        
        tk.Button(self.frame, text="返回主页面", font=("楷体", 11), width=10, height=1, command=self.return_to_start)\
        .place(x=350, y=480)#返回主页面
              
        self.texts = tk.Text(self.frame, font=('FangSong', 12), width=63, height=20)
        self.texts.place(x=150, y=142)  # 如果这行代码不单独写,下面代码就会报错'NoneType' object has no attribute 'insert'
        #添加垂直滚动条
        scrollbar = ttk.Scrollbar(self.frame,command=self.texts.yview)
        scrollbar.place(x=639, y=145, height=318)
        self.texts.config(yscrollcommand=scrollbar.set)
        self.texts.insert(tk.END, '已压缩的照片:\n')
         
    def yasuo(self):
        reg1=re.compile(r'\.(jpg|png)$')
        reg2=re.compile(r'(?<=IMG_)\d+_\d+(?=.jpg)')#匹配\d+_\d+
        folder_path = self.lujing.get()  # 获取已经选择的文件夹路径
        pics = os.listdir(folder_path)
        # 添加路径
        prefix = folder_path
        self.pic = [os.path.join(prefix,str(x)) for x in pics]
        if not all([folder_path]):
            tkinter.messagebox.showerror("无法压缩!", "请填写文件夹路径")
           #获取图片的文件名,并拼接完整路径            `
            self.pic.extend(list_j)
 
        for i in range(len(self.pic)):
            name,exc=osp.split(self.pic[i])#osp.splitext()返回一个文件路径,和文件后缀组成的元组,所以name是一个文件路径。而os.path.split():返回一个路径的目录名和文件名(带后缀)
            nam=osp.basename(name)
            self.dic[nam]=[osp.join(name,exc) for exc in os.listdir(name) if osp.isfile(osp.join(name,exc))
                                                                   if reg1.findall(exc)]#字典的键是唯一的,dic[nam]所以会自动去重
        #print(self.dic)
        #可以利用字典构造DataFrane                                                           
        #df=pd.DataFrame.from_dict(data=dic,orient="columns")
        #进度条
        progress = ttk.Progressbar(self.frame,orient="horizontal",length=155, mode="determinate")
        progress.place(x=500, y=113)
        progress["maximum"] = len(self.pic)#进度条最大值
        progress["value"] = 0#进度条当前值 
        for key,item in self.dic.items():
            #print(f"开始压缩:{key}文件夹内的照片..")
            self.texts.insert(tk.END,f"{key}文件夹内的照片..\n")            
            for file in item:
                im = Image.open(file)#打开里面的jpg文件
                (x,y) = im.size #读取图片尺寸(像素)
                x_s = 2000 #定义缩小后的标准宽度 
                y_s = int(y * x_s / x) #基于标准宽度计算缩小后的高度
                out = im.resize((x_s,y_s),Image.Resampling.LANCZOS) #改变尺寸,保持图片高品质
                out.save(file)#原路径保存
                progress.step(1)
                progress.update()  # 手动更新进度条,没有此代码进度条不显示  
                na,ex=osp.split(file)
                #print(f'{ex}-压缩完成') 
                self.texts.insert(tk.END,f'{ex}-压缩完成\n')
            #print()
            self.texts.insert(tk.END, "\n")#空行
        self.texts.insert(tk.END, f"本次系统共压缩了{len(self.dic)}个照片文件夹"+f'共{len(self.pic)}张照片\n')    
        progress.destroy()#销毁进度条
        tkinter.messagebox.showinfo("压缩完成", f"本次系统共压缩了{len(self.dic)}个照片文件夹"+f',共{len(self.pic)}张照片\n')
 
 
    def return_to_start(self):#返回主页面函数
        self.master.master.notebook.select(0)
    def open_folder(self, event=None):
        folder_path = self.lujing.get()  # 取文本框的值,获取目前Entry的字符串内容
        self.folder_path = folder_path
        self.folder_dialog = filedialog.askdirectory(initialdir=folder_path, title='选择')  # 对话框
        # self.lujing.delete(0, tk.END)  # 清空Entry控件中的内容
        self.lujing.insert(0, self.folder_dialog)  # 将选择的文件夹路径更新到Entry控件中
    def set_entry(self):
        self.lujing.delete(0, tk.END)
        self.texts.delete('1.0', tk.END)  # 清空Text文本框内容
        self.pic=[]#清空照片文件夹列表
        self.dic={}#清空存放文件夹名和文件夹内文件名的键值对字典
 
 
         
 
if __name__ == "__main__":
    app = MyApp()
    app.mainloop()

[GUI]下载抖音分享出来的链接的视频

uupython阅读(887)

这段代码是一个基于 wxPython 的简单的图形界面程序,用于从抖音视频链接中下载视频。用户可以在界面中输入抖音视频链接,然后点击按钮进行下载,下载的结果会显示在界面上。

以下是代码的主要部分:

  1. download_video 函数:该函数用于下载视频文件。它通过请求指定的视频链接,将视频内容保存到指定的文件路径。
  2. get_mid_string 函数:用于从给定的 HTML 内容中提取指定起始字符串和结束字符串之间的内容。
  3. remove_special_characterskeep_characters_before_hash 函数:用于处理视频标题,分别用于去除特殊字符和保留哈希字符前的部分。
  4. download_with_multiprocessing 函数:用于多进程下载视频,获取视频标题和视频链接,并调用 download_video 函数进行下载。
  5. Frame 类:wxPython 程序的主窗口。包括一个文本输入框用于输入视频链接,一个按钮用于触发下载,以及一个用于显示下载结果的文本框。
  6. 按钮1_按钮被单击 方法:按钮点击事件的处理函数。它从编辑框中获取输入的视频链接,使用正则表达式找出链接,然后使用多进程进行视频下载,并将下载结果显示在另一个编辑框中。
  7. myApp 类:wxPython 的应用类,用于启动和运行程序。
  8. if __name__ == '__main__':启动 wxPython 应用程序的入口点。

需要注意的是,代码中使用了 wxPython、requests 和 multiprocessing 模块,确保你已经安装了这些库。此外,多进程下载可以加速下载过程,但也需要根据网络连接和计算机性能来适当调整。

import wx
import re
import requests
from multiprocessing import Pool
 
req=requests.session()
 
def download_video(url, save_path):
    response = requests.get(url, stream=True)
    if response.status_code == 200:
        with open(save_path, 'wb') as file:
            for chunk in response.iter_content(1024):
                file.write(chunk)
        t='下载成功'
    else:
        t='下载失败'
    return t
 
def get_mid_string(html, start_str, end):
    try:
        start = html.find(start_str)
        if start >= 0:
            start += len(start_str)
            end = html.find(end, start)
            if end >= 0:
                return html[start:end].strip()
    except:
        return None
 
def remove_special_characters(text):
    pattern = r'[\\/:*?"<>|]'
    cleaned_text = re.sub(pattern, '', text)
    return cleaned_text
 
def keep_characters_before_hash(text):
    parts = text.split('#', 1)
    return parts[0]
 
def download_with_multiprocessing(url):
    a=req.get('https://api.douyin.wtf/api?url={}'.format(url))
    title = get_mid_string(a.text, '"desc":"', '"')
    title1 = remove_special_characters(title)
    title2 = keep_characters_before_hash(title1)
    video_url = get_mid_string(a.text, 'nwm_video_url":"', '"')
    save_path = f'./{title2}.mp4'
    return download_video(video_url, save_path)
 
class Frame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title='抖音下载', size=(400, 300), name='frame', style=541072960)
        self.启动窗口 = wx.Panel(self)
        self.Centre()
        self.编辑框1 =  wx.TextCtrl(self.启动窗口, size=(269, 221), pos=(7, 7), value='', name='text', style=1073741856)
        self.标签1 = wx.StaticText(self.启动窗口, size=(80, 107), pos=(292, 16), label='默认保存与\n程序相同文\n件夹', name='staticText', style=2321)
        self.标签1.SetForegroundColour((255, 0, 0, 255))
        self.按钮1 = wx.Button(self.启动窗口, size=(80, 32), pos=(287, 215), label='按钮', name='button')
        self.按钮1.Bind(wx.EVT_BUTTON, self.按钮1_按钮被单击)
        self.编辑框2 = wx.TextCtrl(self.启动窗口, size=(98, 22), pos=(282, 134), value='', name='text', style=16)
 
    def 按钮1_按钮被单击(self, event):
        c=self.编辑框1.GetValue()
        pattern = r'[a-zA-Z]+://[^"\'\s]*'
        urls = re.findall(pattern,c)
        with Pool(len(urls)) as pool:
            results = pool.map(download_with_multiprocessing, urls)
 
        s = '\n'.join(results)
        self.编辑框2.SetValue(s)
 
class myApp(wx.App):
    def OnInit(self):
        self.frame = Frame()
        self.frame.Show(True)
        return True
 
if __name__ == '__main__':
    app = myApp()
    app.MainLoop()

过滤谷歌浏览器书签失效的网址

uupython阅读(705)

这段代码是用于过滤失效的谷歌浏览器书签的脚本。它读取一个导出的书签文件(HTML格式),然后检查书签中的链接是否有效。有效的链接会被保留在一个新的HTML文件中,而失效的链接则会被记录在另一个HTML文件中。

以下是代码的主要部分:

  1. main_start 函数:这是主要的处理函数,它打开输入的书签文件以及两个输出文件,一个用于保存有效链接,另一个用于保存失效链接。然后,它逐行读取书签文件中的内容,进行链接的检查和处理。
  2. preg_matchpreg_match_github:这是用于匹配链接的正则表达式模式。preg_match 用于匹配书签中的链接,而 preg_match_github 用于检查是否是 GitHub 链接。
  3. 在书签文件中逐行循环:对于每一行,它首先尝试匹配链接并处理。如果链接匹配成功,它会尝试访问链接并检查响应状态码。如果状态码是 404,表示链接失效,会将链接添加到 bed_html 中。如果链接有效或者发生异常,会将原始行添加到 filter_html 中。
  4. 将有效和失效的链接写入文件:最后,filter_html 中的有效链接会写入到名为 整理后的书签文件.html 的输出文件中,bed_html 中的失效链接会写入到名为 失效的书签文件.bed.html 的输出文件中。
  5. __name__ == '__main__':确保只有在直接运行脚本时才会执行 main_start 函数。

请注意,这段代码使用了 requests 库来访问链接并检查状态码,需要确保你已经安装了这个库。此外,对于链接的有效性检查可能会受到一些因素的影响,例如网络延迟等,因此需要注意脚本的执行时间和效果。

# 过滤失效的谷歌浏览器书签
import re
import requests
 
# 按 Shift+F10 执行或将其替换为您的代码。
# 按 双击 Shift 在所有地方搜索类、文件、工具窗口、操作和设置。
 
def main_start():
    new_file = open('./整理后的书签文件.html', mode='w', encoding='utf-8')
 
    new_file2 = open('./失效的书签文件.bed.html', mode='w', encoding='utf-8')
    # 字符匹配
    preg_match = re.compile('A HREF=".*ADD_DATE')
 
    preg_match_github = re.compile('github')
 
    filter_html = ''
    bed_html = ''
    with open('./谷歌浏览器导出的书签文件', encoding='utf-8') as bookmark:
        num = 0
        for item in bookmark.readlines():
            if num == 10:
                break
            str_match = preg_match.search(item)
            if str_match != None:
                group_str = str_match.group()
                group_str = group_str.replace('A HREF="', '')
                group_str = group_str.replace('" ADD_DATE', '')
                print(f' 开始请求:{group_str}')
                try:
                    github_search = preg_match_github.search(group_str)
                    if github_search != None:
                        filter_html += item + '\n'
                    else:
                        rr = requests.get(group_str, timeout=5)
                        if rr.status_code == 404:
                            bed_html += group_str + '\n'
                            print(f'{group_str} 这个网址失效啦!')
                        else:
                            filter_html += item + '\n'
                            print(f' 成功响应:{group_str}')
                except:
                    bed_html += group_str + '\n'
                    print(f'except:{group_str} 这个网址失效啦!')
            else:
                filter_html += item + '\n'
 
        new_file.write(filter_html)
        new_file2.write(bed_html)
 
 
# 按间距中的绿色按钮以运行脚本。
if __name__ == '__main__':
    main_start()

[GUI]字符串转换MD5

uupython阅读(430)

这段代码创建了一个基于Tkinter的简单GUI应用程序,用于将输入的字符串转换为MD5哈希值并显示在窗口中。

代码主要实现了以下功能:

  1. convert_to_md5 函数:当用户点击”转换为MD5″按钮时,该函数被调用。它从输入框获取字符串,计算其MD5哈希值,并将哈希值显示在输出框中。
  2. 创建GUI窗口:
  • 使用 tk.Tk() 创建一个GUI窗口。
  • 设置窗口标题和尺寸。
  1. 创建输入框:
  • 创建一个标签用于显示 “输入字符串” 提示。
  • 创建一个输入框,用于输入要转换的字符串。
  1. 创建转换按钮:
  • 创建一个按钮,上面显示 “转换为MD5” 文字。
  • 为按钮绑定 convert_to_md5 函数,点击按钮时调用该函数。
  1. 创建输出框:
  • 创建一个标签用于显示 “MD5值” 提示。
  • 创建一个多行文本框,用于显示转换后的MD5哈希值。
  1. 使用 window.mainloop() 启动GUI主循环,使窗口显示并等待用户交互。

用户可以在输入框中输入任意字符串,然后点击 “转换为MD5” 按钮,即可在输出框中看到该字符串的MD5哈希值。

请注意,这只是一个简单的示例,GUI应用程序的功能和界面可以根据需要进行扩展和美化。

import hashlib
import tkinter as tk
def convert_to_md5():
    input_str = input_text.get()
    md5_hash = hashlib.md5(input_str.encode()).hexdigest()
    output_text.delete(1.0, tk.END)
    output_text.insert(tk.END, md5_hash)
 # 创建GUI窗口
window = tk.Tk()
window.title("字符串转换MD5小工具")
window.geometry("500x300+100+200")
 # 创建输入框
input_label = tk.Label(window, text="输入字符串:")
input_label.pack()
input_text = tk.Entry(window)
input_text.pack()
 # 创建转换按钮
convert_button = tk.Button(window, text="转换为MD5", command=convert_to_md5)
convert_button.pack()
 # 创建输出框
output_label = tk.Label(window, text="MD5值:")
output_label.pack()
output_text = tk.Text(window, height=5, width=30)
output_text.pack()
window.mainloop()

爬取性感小姐姐写真全套--2meinv

uupython阅读(1072)

这段代码实现了多进程下载妹子图网站(https://www.2meinv.com/)的图片。使用了requestsgevent库来进行并发下载操作,多进程用于加速不同图片集的下载。代码主要涵盖以下几个部分:

  1. get_last_page: 从链接中提取页数。
  2. image_downLoad: 下载单张图片。
  3. img_req: 解析单个图片集的页面,获取图片链接和标题。
  4. 主程序部分:
  • save_path: 图片保存的目录。
  • startend: 图片集的起始和结束ID,表示需要下载哪些图片集的图片。
  • process_num: 每次启动的进程数。

主要逻辑如下:

  1. 循环下载图片集,每次启动 process_num 个进程。
  2. 每个进程调用 img_req 函数,解析图片集的每一页,并使用 gevent 并发下载图片。
  3. 各个进程会并行下载不同的图片集。

这段代码是一个多进程并发下载的示例,但在实际应用中需要注意合法使用爬虫,避免给网站服务器造成过大负担,以及遵守相关法律法规。此外,爬取网站的图片涉及版权问题,需要注意尊重版权。

加入了多进程和协程,提高了下载速度。同时具备了异常处理机制,出现错误不会中断程序.

import os
import re
import time
import multiprocessing
import gevent
from gevent import monkey
from lxml import etree
 
monkey.patch_all()
import requests
 
headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/91.0.4472.114 Safari/537.36 Edg/91.0.864.59',
    'Referer': 'https://www.2meinv.com/'
}
 
 
def get_last_page(text):
    return int(re.findall('[^/$]\d*', re.split('/', text)[-1])[0])
 
 
def image_downLoad(img_url, img_save_path, img_name):
    try:
        img_file = img_save_path + img_name
        img_data = requests.get(url=img_url, headers=headers).content
        if not os.path.exists(img_save_path):
            os.makedirs(img_save_path)
        if not os.path.exists(img_file):
            with open(img_file, 'wb') as fp:
                fp.write(img_data)
                print(img_name, '图片下载成功!!!')
    except BaseException as e:
        print(img_url, '下载图片出错了!')
 
 
def img_req(urls, path):
    try:
        no = re.findall('[^-$][\d]', urls)[1] + re.findall('[^-$][\d]', urls)[2]
        res = requests.get(urls + ".html", headers=headers).content
        r_tree = etree.HTML(res)
        pages = r_tree.xpath('/html/body/div[2]/div/h1/span')[0].text
        last_page = get_last_page(pages)
    except BaseException as e:
        print(no, '出错了')
        return
    img_list = []
    for i in range(2, last_page + 1):
        try:
            next_url = urls + "-" + str(i) + ".html"
            print('开始解析:', next_url)
            time.sleep(1)
            _next = requests.get(next_url, headers=headers).content
            _next_html = etree.HTML(_next)
            img_url = _next_html.xpath('/html/body/div[5]/a/img/@src')[0]
            title = _next_html.xpath('/html/body/div[5]/a/img/@alt')[0]
            img_name = img_url.split("/")[-1]
            img_save_path = path + title + "/"
            img_list.append({'img_url': img_url, 'img_save_path': img_save_path, 'img_name': img_name})
        except BaseException as e:
            print(title, '图片地址解析出错了')
    g = []
    for im in img_list:
        g.append(gevent.spawn(image_downLoad, im['img_url'], im['img_save_path'], im['img_name']))
    gevent.joinall(g)
    print(no, '下载完成了!')
 
 
if __name__ == '__main__':
    save_path = "G:/spider/image/2meinv/"
    start = 5000
    end = 2000
    process_num = 10
    while start > end:
        process = []
        for i in range(start, (start - process_num) - 1, -1):
            urls = 'https://www.2meinv.com/article-' + str(i)
            m_p = multiprocessing.Process(target=img_req, args=(urls, save_path))
            m_p.start()
            process.append(m_p)
        for k in process:
            k.join()
        start -= process_num

韩漫爬虫(需要科学)

uupython阅读(1380)

这段代码实现了从一个漫画网站爬取漫画信息和漫画图片,并保存到本地的功能。代码包含以下几个主要部分:

  1. display_progress: 显示下载进度的函数,输出章节信息和下载进度。
  2. create_folder: 创建漫画文件夹和章节子文件夹的函数。
  3. download_cover: 下载漫画封面的函数。
  4. comic_chapter: 分析漫画章节信息的函数,返回漫画名称、章节名称、封面链接以及各章节的链接列表。
  5. download_comic_img: 下载漫画图片并保存到对应文件夹的函数。
  6. page: 请求漫画链接页面的函数,返回漫画链接列表。

if __name__ == '__main__':部分:

  1. 使用page函数获取漫画链接列表。
  2. 获取第一个漫画链接的漫画名称和封面链接,创建漫画文件夹和章节文件夹,然后下载漫画封面。
  3. 遍历漫画链接列表,对每个漫画链接获取章节名称和链接,创建章节文件夹,然后遍历章节链接列表,下载漫画图片。

这段代码是一个基本的漫画爬虫,但在实际应用中需要注意遵守网站的规定和法律法规,避免对网站服务器造成过大负担,避免侵权问题。

import os
import re
import time
import requests
from lxml import html
 
 
# 输出章节信息和下载进度
def display_progress(comic_name, chapter_name, current_page_idx, total_pages):
    """
    显示下载进度
    """
    chapter_name = chapter_name.strip()
    print(f"\n漫画名称:{comic_name}")
    print(f"章节名称:{chapter_name}")
    print(f"正在下载第 {current_page_idx}/{total_pages} 张漫画图片...")
 
 
# 创建漫画文件夹和章节文件夹
def create_folder(comic_name, chapter_names):
    """
    在当前目录下创建漫画文件夹及各章节子文件夹,返回漫画文件夹路径和章节文件夹名称列表
    """
    # 创建漫画文件夹
    comic_dir_name = comic_name.strip()
    comic_dir_name = re.sub(r'[\\/:*?"<>|.]', '', comic_dir_name)
    if not os.path.exists(comic_dir_name):
        os.mkdir(comic_dir_name)
 
    chapter_dir_names = []  # 存储各章节文件夹名称
    for chapter_name in chapter_names:
        # 创建各章节子文件夹
        chapter_dir_name = chapter_name.strip()
        chapter_dir_name = re.sub(r'[\\/:*?"<>|.]', '', chapter_dir_name)
        # chapter_dir_name = chapter_dir_name.replace(' ', '')  # 删除空格
        chapter_dir_name = os.path.join(comic_dir_name, chapter_dir_name)
        if not os.path.exists(chapter_dir_name):
            os.mkdir(chapter_dir_name)
        chapter_dir_names.append(chapter_dir_name)
    return comic_dir_name, chapter_dir_names
 
 
# 下载封面到漫画名文件夹中
def download_cover(cover_url, comic_dir_name):
    print(f"漫画封面链接为 {cover_url}")
    response = requests.get(cover_url)
    if response.status_code == 200:
        with open(os.path.join(comic_dir_name, 'cover.jpg'), 'wb') as f:
            f.write(response.content)
        print("漫画封面下载成功")
    else:
        print(f"漫画封面下载失败,状态码:{response.status_code}")
 
 
# 分析章节
def comic_chapter(comic_link):
    response = requests.get('https://www.sesemanhua.top' + comic_link)
    if response.status_code != 200:  # 判断是否请求成功
        print("请求失败")
        return [], "", "", []
    chapter_html_content = response.text
    etree = html.etree
    html_ = etree.HTML(chapter_html_content)
    # 漫画名
    comic_name = html_.xpath("//div[@class='de-info__cover']/img/@alt")
    if not comic_name:
        print("漫画名称获取失败")
        return [], "", "", []
    comic_name = comic_name[0]
    # 漫画封面
    cover_url = html_.xpath("//div[@class='de-info__cover']/img/@src")
    if not cover_url:
        print("漫画封面链接获取失败")
        return [], "", "", []
    cover_url = cover_url[0]
    # 章节名称
    chapter_names = html_.xpath("//div[@class='de-chapter']//a/text()")
    if not chapter_names:
        print("章节名称获取失败")
        return [], "", "", []
    # 章节连接
    chapter_links = html_.xpath("//div[@class='de-chapter']//a[@class='j-chapter-link']/@href")
    if not chapter_links:
        print("章节链接获取失败")
        return [], "", "", []
    result = []
    for url in chapter_links:
        # 组合出链接
        chapter_link = ('https://www.sesemanhua.top' + url)
        result.append(chapter_link)
    # 返回章节名称和章节链接列表
    return chapter_names, comic_name, cover_url, result
 
 
# 下载漫画图片并保存在对应文件夹
def download_comic_img(comic_name, chapter_name, chapter_link, comic_dir_name, chapter_dir_name):
    chapter_name = chapter_name.strip()
    comic_name = re.sub(r'[\\/:*?"<>|.]', ' ', comic_name)
    chapter_name = re.sub(r'[\\/:*?"<>|.]', ' ', chapter_name)
    response = requests.get(chapter_link)
    if response.status_code != 200:  # 判断是否请求成功
        print(f"请求失败,状态码:{response.status_code}")
        return
 
    comic_html_image_content = response.text
    etree = html.etree
    html_ = etree.HTML(comic_html_image_content)
    img_src_list = html_.xpath("//div[@class='rd-article-wr clearfix']//img/@data-original")
    num_pages = len(img_src_list)
 
    # 判断该章节是否已经下载完成,如果已经下载完成,则跳过该章节,进行下一个章节的下载
    if len(os.listdir(chapter_dir_name)) == num_pages:
        print(f"\n{chapter_name} 已下载完成!")
        return
 
    print(f"\n共获取到{num_pages}张图片,当前已下载{len(os.listdir(chapter_dir_name))}张图片")
    # 判断是否下载中断,如果是,则跳过已下载的部分,从中断的位置开始下载
    start_idx = len(os.listdir(chapter_dir_name))
    if start_idx > 0:
        img_src_list = img_src_list[start_idx:]
 
    for index, img_src in enumerate(img_src_list):
        display_progress(comic_name, chapter_name, index + 1 + start_idx, num_pages)
        img_name = str(index + 1 + start_idx).zfill(3) + '.jpg'
        # 如果该文件已经存在于本地文件夹中,则跳过该文件的下载
        if os.path.exists(os.path.join(chapter_dir_name, img_name)):
            print(f"第{index + 1 + start_idx}张图片已存在,跳过下载")
            continue
 
        # 保存到目录中
        response = requests.get(img_src)
        if response.status_code == 200:
            content = response.content
            with open(os.path.join(chapter_dir_name, img_name), 'wb') as f:
                f.write(content)
            print(f"第{index + 1 + start_idx}张图片下载完成")
            time.sleep(5)
        else:
            print(f"第{index + 1 + start_idx}张图片下载失败,状态码:{response.status_code}")
 
        # 下载过程中若出现中断,则尝试重新下载该章节的图片
        if index + 1 + start_idx < num_pages:
            if len(os.listdir(os.path.join(chapter_dir_name))) < index + 2 + start_idx:
                print(f"第{index + 1 + start_idx + 1}张图片下载失败,尝试重新下载...")
                download_comic_img(comic_name, chapter_name, chapter_link, comic_dir_name, chapter_dir_name)
                break
 
    print()
 
 
# 请求漫画链接页面
def page():
    for page in range(1, 21):
        # 请求网页内容
        url = f'https://www.sesemanhua.top/index.php/category/order/addtime/page/{page}'
        response = requests.get(url)
        if response.status_code != 200:  # 判断是否请求成功
            print("请求失败")
            return []
        page_html_content = response.text
        # 使用XPath解析漫画链接
        etree = html.etree
        html_ = etree.HTML(page_html_content)
        # 漫画链接
        comic_links = html_.xpath("//div[@class='cate-comic-list clearfix']//p[@class='comic__title']/a/@href")
        # 打印当前页面的漫画链接和状态
        print(f"正在爬取第{page}页...")
        for link in comic_links:
            if 'javascript:void' in link:
                continue  # 跳过无效链接
            print('https://www.sesemanhua.top
' + link)  # 漫画链接
        return comic_links
 
 
if __name__ == '__main__':
    # 获取漫画链接、名称和封面图片链接
    comic_links = page()
    if not comic_links:
        exit()
    _, comic_name, cover_url, _ = comic_chapter(comic_links[0])  # 修改此处,取第一话的漫画名称和封面链接
    # 创建漫画文件夹和章节文件夹
    _, chapter_dir_names = create_folder(comic_name, [])
    # 下载漫画封面
    download_cover(cover_url, comic_name)
    # 遍历漫画链接并下载漫画
    for comic_link in comic_links:
        # 获取章节名称和链接
        chapter_names, comic_name, _, chapter_links = comic_chapter(comic_link)
        if not chapter_links:
            continue
        # 创建章节文件夹
        _, chapter_dir_names = create_folder(comic_name, chapter_names)
        # 下载漫画图片
        for i in range(len(chapter_links)):
            download_comic_img(comic_name, chapter_names[i], chapter_links[i], comic_name, chapter_dir_names[i])

协程异步抓取影院加密视频

uupython阅读(405)

这段代码是一个使用aiohttp库和asyncio库来下载和处理M3U8视频的脚本。M3U8是一种视频播放列表格式,常用于分片视频流的传输。

主要步骤如下:

  1. 导入需要的库,设置一些变量,如path为保存视频文件的路径,url为M3U8视频的URL,ts_url用于保存分片视频的URL。
  2. getindex_m3u8函数获取M3U8播放列表文件,解析出index.m3u8enc.m3u8的URL,并请求这两个文件的内容。
  3. mkdir_path函数用于创建文件夹、保存index.m3u8enc.m3u8,并对index.m3u8文件进行转换,重命名其中的TS文件名,并将TS的URL保存在ts_url列表中。
  4. download函数用于异步下载TS分片文件。
  5. ffmpeg函数使用FFmpeg将所有的TS文件合并成一个MP4视频。
  6. main函数是整个脚本的主控制函数。在main函数中,先调用getindex_m3u8获取播放列表文件内容,然后调用mkdir_path进行目录创建和文件保存,接着使用asyncio.gather并发下载所有的TS分片文件,最后使用ffmpeg函数将下载的分片文件合并为一个MP4视频。
  7. if __name__ == '__main__':部分,调用asyncio.run(main())来执行整个异步任务。

这段代码实现了使用异步方法下载、合并M3U8视频的功能。注意,由于涉及到视频下载和处理,确保你了解所使用的M3U8格式和视频下载的合法性。

import asyncio
import os.path
import re
import os

import aiohttp
import requests

path = '影院'
# 自行替换该影院URL 主页:http://www.cdhxjdsb.com/
url = 'http://www.cdhxjdsb.com/dongzuopian/zhetianjinqu/1-1.html'
ts_url = []
headers = {
    'User-Agent':
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'
}

async def getindex_m3u8(url):
    '''

    :param url: 要下载的视频URL
    :return: 返回m3u8和key的数据
    '''
    # 获取index.m3u8的链接
    async with aiohttp.ClientSession(headers=headers) as session:
        async with session.get(url, verify_ssl=False) as get_url:
            data = await get_url.text()
    r = re.search('"url":"(.*?)"', data).group(1)
    # 去除转义符获得正确的index.m3u8链接
    env_key = r.strip().replace("\\", '')
    print(env_key)
    # 获取key加密文件后边进行解密
    key = env_key.replace('index.m3u8', 'enc.key')
    print(key)
    # 请求m3u8链接数据
    async with aiohttp.ClientSession(headers=headers) as session2:
        async with session2.get(env_key) as gei_m3u8:
            m3u8 = await gei_m3u8.text()

    # 请求key数据
    async with aiohttp.ClientSession(headers=headers) as session3:
        async with session3.get(key) as gei_key:
            m3u8_key = await gei_key.text()

    return m3u8, m3u8_key

async def mkdir_path(m3u8, m3u8_key):
    '''

    :param m3u8: 传入数据
    :param m3u8_key: 传入数据
    :return:
    '''
    # 创建文件夹,进行路径判断
    if not os.path.exists(path):
        # 创建文件夹
        os.mkdir(path)
        # 获取到的m3u8进行写入保存
    with open(os.path.join(path, 'index.m3u8'), 'w') as f:
        f.write(m3u8)
        # 获取到的key进行写入保存
    with open(os.path.join(path, 'enc.m3u8'), 'w') as a:
        a.write(m3u8_key)
        # 进行读取转换成自己的ts文件名
    with open(os.path.join(path, 'index.m3u8'), 'r') as r:
        date = r.readlines()
    aa = open(os.path.join(path, 'index2.m3u8'), 'w')
    e = 0
    # 遍历原m3u8文件
    for i in date:
        # 判断进行转换成自己的ts文件名
        if i.startswith("#"):
            # 判断URL是否存在
            if i.find('URI') != -1:
                uu = i
                i = re.sub(r'(#EXT-X-KEY:METHOD=AES-128,URI=)"(.*?)"',
                           f'#EXT-X-KEY:METHOD=AES-128,URI="enc.m3u8"', uu)
            aa.write(i)
            continue
        else:
            # 重写ts文件名
            aa.write(str(e) + '.ts' + '\n')
            ts_url.append(i.strip())
        e += 1

async def dowmlaod(index, value):
    '''

    :param index: 接受下标
    :param value: 接受URL
    :return:
    '''
    # 请求URL获取字节数据
    async with aiohttp.ClientSession(headers=headers) as session:
        async with session.get(value, verify_ssl=False) as get_url:
            data = await get_url.content.read()
        # 开始写入保存ts视频文件
        with open(os.path.join(path, f'{index}.ts'), 'wb') as f:
            f.write(data)
            print('下载完成' + str(index))

#
# # '''
# # 进行ts文件合并 解决视频音频不同步的问题 建议使用这种
# #   :param filePath:
# #   :return:
# # '''
# print('开始')
#
# ccc = os.chdir(path)
#
# os.system("ffmpeg -i index2.m3u8 -c copy cccc.mp4")
# print('结束')

async def ffmpeg():
    '''
    用ffmpeg合并处理加密
    :return: 
    '''
    print('开始')
    ccc = os.chdir(path)
    os.system("ffmpeg -i index2.m3u8 -c copy cccc.mp4")
    print('结束')

async def main():
    m3u8, m3u8_key = await getindex_m3u8(url)
    print(m3u8)
    print(m3u8_key)
    await mkdir_path(m3u8, m3u8_key)
    task = []
    # 遍历下载所有ts文件 (获取ts的URL和索引)
    for index, value in enumerate(ts_url):
        task.append(dowmlaod(index,value))

    await asyncio.gather(*task)
    await ffmpeg()

if __name__ == '__main__':
    asyncio.run(main())

获取天气预报

uupython阅读(506)

这段代码是一个简单的天气查询工具,使用了OpenWeatherMap的免费API来获取天气预报数据,并将结果以格式化字符串的形式输出。

主要步骤如下:

  1. Current_Date函数使用datetime模块获取当前的年、月、日,并将它们格式化为字符串,返回形如”年-月-日”的日期字符串。
  2. Get_Weather函数用于获取地区的天气预报。用户需要输入一个英文地区名称,然后使用OpenWeatherMap API查询该地区的天气信息。API的URL中包含了用户输入的地区名称,API Key以及其他参数,其中lang=zh_cn表示返回中文描述。函数从API的JSON响应数据中提取出温度、地区名称和天气状况描述,然后使用格式化字符串构建返回的天气预报信息。
  3. 在主程序中,调用Get_Weather函数获取天气预报数据,并输出到控制台。

请注意,这段代码使用了OpenWeatherMap的免费API,需要替换其中的API Key,且免费版有一定的请求限制。另外,API的响应数据结构可能会有变化,需要根据具体情况来解析JSON数据。

import requests                     # 导入 requests 库,用于发送 HTTP 请求
import json                         # 导入 json 库,用于处理 JSON 数据
from datetime import datetime       # 从 datetime 模块导入 datetime 类
# 获取当前时间
def Current_Date():
    current_time = datetime.now()
    # 提取年、月和日
    year = current_time.year
    month = current_time.month
    day = current_time.day
    return '%s年-%s月-%s日' % (year, month, day)
 
# 获取地区天气预报
def Get_Weather():
    location = input("请输入需要查询天气地区的英文名称: ")   # 用户输入需要查询的地区名称
    # 天气预报免费版接口api
    url = "http://api.openweathermap.org/data/2.5/weather?q=%s&lang=zh_cn&appid=12b2817fbec86915a6e9b4dbbd3d9036" % location   
    print(url)
    # 构造天气预报 API 的 URL,其中 %s 会被用户输入的地区名称替代
    data = requests.get(url).json()   # 发送 GET 请求并将响应转换为 JSON 格式的数据
    temperature = data["main"]["temp"]   # 从 JSON 数据中提取出温度信息
    city = data["name"]        # 从 JSON 数据中提取出地区名称
    description = data["weather"][0]["description"]   # 从 JSON 数据中提取出天气状况描述
 
    # '%s,%s的天气温度是%s摄氏度,天气状况是%s' 是一个格式化字符串,其中 %s 是占位符。这些占位符会被后面的参数依次替换,生成最终的字符串。
 
    return '%s,%s的天气温度是%s摄氏度,天气状况是%s' % (Current_Date(), city, temperature, description)
 
return_data = Get_Weather()   # 调用函数获取天气预报的返回数据
print(return_data)   # 输出天气预报的返回数据

带彩色进度条的笔趣阁爬虫

uupython阅读(562)

这段代码是一个简单的小说爬虫,使用requests库和re模块来爬取小说章节内容。它通过获取章节目录,逐一下载章节内容并保存到本地的”小说.txt”文件中。

主要步骤如下:

  1. 创建一个名为Spider的类,其中包含初始化方法__init__
  2. getcatalog方法用于获取小说的章节目录,它发送GET请求获取页面内容,然后使用正则表达式提取章节链接和标题,将这些数据存储在self.data列表中。
  3. parse方法用于解析小说章节内容,也是通过发送GET请求获取页面内容,然后使用正则表达式提取章节文本。
  4. download方法用于下载小说内容。它循环遍历self.data中的章节链接,依次调用parse方法解析并写入章节内容到文件中。同时,使用rich库中的Progress类来显示下载进度条。
  5. 在主程序中,创建一个Spider对象,从而触发爬虫的运行。

请注意,这段代码只是一个简单的示例,实际爬虫可能会面临更多的挑战和问题,例如反爬措施、异常处理等。在进行网络爬取时,也需要遵循网站的规则和法律法规。

如果要把开头的猴子捂脸表情改成月亮,就把monkey改为moon!

import requests, re
from rich.progress import Progress
from rich.progress import BarColumn,Progress,SpinnerColumn,TaskProgressColumn,TimeElapsedColumn,TimeRemainingColumn,MofNCompleteColumn
class Spider:
    url = "http://www.ibiquge.cc/83110"
    baseurl = "http://www.ibiquge.cc"
    headers = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36'
    }
    def __init__(self) -> None:
        self.getcatalog()
        self.download()
    #获取章节目录
    def getcatalog(self):
        self.data = []
        rsp = requests.get(self.url, headers=self.headers)
        purl = '<dd><a href ="(.*?)">'
        ptitle = '<dd><a href =".*?>(.*?)<'
        for i, j in zip(re.findall(purl, rsp.text),re.findall(ptitle, rsp.text)):
            self.data.append([j, self.baseurl+i])
    #下载
    def download(self):
        with open('小说.txt', 'a') as f:
            #设置进度条参数
            with Progress(SpinnerColumn(spinner_name='monkey', speed=0.2),"{task.description}",BarColumn(),MofNCompleteColumn(),TaskProgressColumn(),
                      TimeElapsedColumn(),TimeRemainingColumn(),) as progress:
                track = progress.add_task(total=len(self.data), description='downloading')
                progress.update(track, advance=0)  # 初始化进度条
                for i in self.data:
                    f.write(i[0]+'\n')
                    txt = self.parse(i[1])
                    f.writelines(txt)
                    progress.update(track, advance=1,description='downloading')
    #解析小说内容
    def parse(self, url):
        rsp = requests.get(url,headers=self.headers)
        content = rsp.text
        p = '>        ([\s\S]*?)<'
        txt = re.findall(p, rsp.text)
        return txt #txt is list
Spider()

【web】在线文字转语音edge_tts web服务端

uupython阅读(631)

预览地址:https://tts.zhu.ee

1.服务端代码

from fastapi import FastAPI, Request, Form, File
from fastapi.responses import JSONResponse, FileResponse
from fastapi.templating import Jinja2Templates
import edge_tts  #pip install edge-tts
import asyncio
import hashlib
import datetime
import os
import tempfile
import pygame.mixer  # sudo apt-get install libsdl2-dev
import uvicorn

pygame.mixer.init()

app = FastAPI()
templates = Jinja2Templates(directory="templates")


async def my_function(text, output, voice, rate):
    volume = '+0%'
    tts = edge_tts.Communicate(text=text, voice=voice, rate=rate, volume=volume)
    await tts.save(output)


@app.get("/")
async def index(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})


@app.post("/synthesize")
async def synthesize(request: Request):
    data = await request.json()
    text = data.get("text")
    voice = data.get("voice")
    rate = data.get("rate")
    output_dir = os.path.join(os.path.dirname(__file__), "mp3")
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    # 构造文件名
    now = datetime.datetime.now()
    filename_base = hashlib.md5((text[:5] + str(now.timestamp())).encode()).hexdigest()
    filename = os.path.join(output_dir, filename_base + ".mp3")

    with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3", dir=output_dir) as temp_file:
        temp_filename = temp_file.name
        await my_function(text, temp_filename, voice, rate)

    # 将临时文件转存为指定的输出文件
    os.rename(temp_filename, filename)

    # 返回包含下载链接的响应
    return JSONResponse(content={"message": "success", "download_link": f"/download?filename={filename}"})


@app.get("/download")
async def download(filename: str):
    file_path = os.path.join(app.root_path, filename)
    return FileResponse(path=file_path, filename=filename, media_type="application/octet-stream")


if __name__ == "__main__":
    uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)
    # http://localhost:8000/

2.web界面代码(html+js)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>在线语音合成 Powered By ChatGPT</title>
<meta name="keywords" content="文字转语音,语音合成,微软语音合成,免费文本转语音,免费语音合成,在线文字转语音">
<meta name="description" content="在线免费语音合成,多种音源选择,支持语速调节!">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.0-alpha1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background-color: #f8f9fa;
}
.container {
max-width: 700px;
}
h1 {
font-size: 2.5rem;
font-weight: 700;
color: #333;
}
form {
background-color: #fff;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
label {
font-size: 1rem;
font-weight: 700;
}
#synthesize-button {
background-color: #007bff;
border-color: #007bff;
}
#synthesize-button:hover {
background-color: #0069d9;
border-color: #0062cc;
}
#play-button {
background-color: #28a745;
border-color: #28a745;
}
#play-button:hover {
background-color: #218838;
border-color: #1e7e34;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="container">
<h1 class="text-center my-5">语音合成v1.0</h1>
<form id="synthesize-form">
<div class="mb-3">
<label for="text" class="form-label">请输入要转换为语音的文本:</label>
<textarea id="text" name="text" rows="4" class="form-control"></textarea>
</div>
<div class="mb-3">
<label for="voice" class="form-label">请选择要使用的语音角色:</label>
<select id="voice" name="voice" class="form-select">
<option value="zh-CN-XiaoxiaoNeural">小小-女</option>
<option value="zh-CN-XiaoyiNeural">小逸-女</option>
<option value="zh-CN-YunjianNeural">云剑-男</option>
<option value="zh-CN-YunxiNeural">云希-男</option>
<option value="zh-CN-YunxiaNeural">云夏-男</option>
<option value="zh-CN-YunyangNeural">云阳-男</option>
<option value="zh-CN-liaoning-XiaobeiNeural">辽宁小贝-女</option>
<option value="zh-CN-shaanxi-XiaoniNeural">陕西小妮-女</option>
<option value="zh-HK-HiuGaaiNeural">香港卓嘉-女</option>
<option value="zh-HK-HiuMaanNeural">香港卓萌-女</option>
<option value="zh-HK-WanLungNeural">香港万隆-男</option>
<option value="zh-TW-HsiaoChenNeural">台湾小陈-女</option>
<option value="zh-TW-HsiaoYuNeural">台湾小雨-女</option>
<option value="zh-TW-YunJheNeural">台湾云哲-男</option>
</select>
</div>
<div class="mb-3">
<label for="rate" class="form-label">调整语速:</label>
<select id="rate" name="rate" class="form-select">
<option value="-100%">-100</option>
<option value="-90%">-90</option>
<option value="-80%">-80</option>
<option value="-70%">-70</option>
<option value="-60%">-60</option>
<option value="-50%">-50</option>
<option value="-40%">-40</option>
<option value="-30%">-30</option>
<option value="-20%">-20</option>
<option value="-10%">-10</option>
<option value="+0%" selected>正常</option>
<option value="+10%" >+10</option>
<option value="+20%">+20</option>
<option value="+30%">+30</option>
<option value="+40%">+40</option>
<option value="+50%">+50</option>
<option value="+60%">+60</option>
<option value="+70%">+70</option>
<option value="+80%">+80</option>
<option value="+90%">+90</option>
<option value="+100%">+100</option>
</select>
</div>
<div class="text-center mb-3">
<button type="button" id="synthesize-button" class="btn btn-primary">合成音频</button>
</div>
<div class="text-center mb-3">
<button type="button" id="play-button" class="btn btn-success" style="display:none;">试听</button>
<div id="progress-container" class="progress mt-2" style="display:none; max-width: 200px; margin-left: auto; margin-right: auto;">
<div id="progress-bar" class="progress-bar progress-bar-striped" role="progressbar" style="width: 0%;" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<p id="download-link" class="d-inline-block mt-3" style="display:none;"></p>
</div>
</form>
</div>
</form>
</div>
<script>
var audio;
var isPlaying = false;
$("#synthesize-button").on("click", function(event) {
$(this).prop("disabled", true).text("生成中...");
requestSynthesis();
});
$("#play-button").on("click", function(event) {
if (isPlaying) {
audio.pause();
audio.currentTime = 0;
isPlaying = false;
$("#play-button").text("试听");
$("#progress-container").slideUp(); // Hide progress container
} else {
audio.play();
isPlaying = true;
$("#play-button").text("停止");
$("#progress-container").slideDown(); // Show progress container
 
// Update progress bar
audio.addEventListener("timeupdate", function () {
var progress = (audio.currentTime / audio.duration) * 100;
$("#progress-bar").css("width", progress + "%").attr("aria-valuenow", progress);
});
 
// Reset progress bar on ended
audio.addEventListener("ended", function () {
isPlaying = false;
$("#play-button").text("试听");
$("#progress-container").slideUp(); // Hide progress container
$("#progress-bar").css("width", 0 + "%").attr("aria-valuenow", 0);
});
}
});
 
function requestSynthesis() {
var form_data = {
'text': $("#text").val(),
'voice': $("#voice").val(),
'rate': $("#rate").val()
}
$.ajax({
url: "/synthesize",
type: "POST",
contentType: "application/json",
data: JSON.stringify(form_data),
success: function(response){
var download_link = response['download_link'];
$("#download-link").html( `<a href="${download_link}" class="btn btn-success" download>下载语音文件</a>` ).show();
audio = new Audio(download_link);
isPlaying = false;
$("#synthesize-button").prop("disabled", false).text("合成音频");
$("#play-button").show().text("试听");
audio.addEventListener("ended", function () {
isPlaying = false;
$("#play-button").text("试听");
});
}
});
}
</script>
</body>
<footer style="text-align: center; padding: 20px 0; font-size: 14px; font-weight: 400; color: #777; margin-top: 100px; border-top: 1px solid #e6e6e6;">
&#169; 2023年4月22日 由 <a href="https://www.openai.com/" style="color: #333; text-decoration: none;">ChatGPT</a> 生成
</footer>
</html>