diff --git a/README.md b/README.md index e2f4eb3..a1e58fa 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ pip install -r requirements.txt > - 有关自动切片的配置在 `bilive.toml` 文件的 `[slice]` 部分。 > - `auto_slice` 默认为 false, 即不进行自动切片。 -MLLM 模型主要用于自动切片后的切片标题生成,此功能默认关闭,如果需要打开请将 `auto_slice` 参数设置为 `true`。其他配置分别有: +MLLM 模型主要用于自动切片后的切片标题生成,此功能默认关闭,如果需要打开请将 `auto_slice` 参数设置为 `true`,并且写下你自己的 prompt,其他配置分别有: - `slice_duration` 以秒为单位设置切片时长(不建议超过 180 秒)。 - `slice_num` 设置切片数量。 - `slice_overlap` 设置切片重叠时长。切片采用滑动窗口法处理,细节内容请见 [auto-slice-video](https://github.com/timerring/auto-slice-video) @@ -178,11 +178,11 @@ MLLM 模型主要用于自动切片后的切片标题生成,此功能默认关 接下来配置模型有关的 `mllm_model` 参数即对应的 api-key,请自行根据链接注册账号并且申请对应 api key,填写在对应的参数中,请注意以下模型只有你在 `mllm_model` 参数中设置的那个模型会生效。 -| Company | Google | 智谱 | 阿里云 | -|----------------|-------------------|------------------|-----------------------| -| Name | Gemini-2.0-flash | GLM-4V-PLUS | Qwen-2.5-72B-Instruct | -| `mllm_model` | `gemini`| `zhipu` | `qwen` | -| `API key` | [gemini_api_key](https://aistudio.google.com/app/apikey) | [zhipu_api_key](https://www.bigmodel.cn/invite?icode=shBtZUfNE6FfdMH1R6NybGczbXFgPRGIalpycrEwJ28%3D) | [qwen_api_key](https://bailian.console.aliyun.com/?apiKey=1) | +| Company | Alicloud | zhipu | Google | +|----------------|-----------------------|------------------|-------------------| +| Name | Qwen-2.5-72B-Instruct | GLM-4V-PLUS | Gemini-2.0-flash | +| `mllm_model` | `qwen` | `zhipu` | `gemini` | +| `API key` | [qwen_api_key](https://bailian.console.aliyun.com/?apiKey=1) | [zhipu_api_key](https://www.bigmodel.cn/invite?icode=shBtZUfNE6FfdMH1R6NybGczbXFgPRGIalpycrEwJ28%3D) | [gemini_api_key](https://aistudio.google.com/app/apikey) | #### 2.3 Image Generation Model(自动生成视频封面) @@ -191,8 +191,7 @@ MLLM 模型主要用于自动切片后的切片标题生成,此功能默认关 > - 有关自动生成视频封面的配置在 `bilive.toml` 文件的 `[cover]` 部分。 > - `generate_cover` 默认为 false, 即不进行自动生成视频封面。 -采用图生图多模态模型,自动获取视频截图并上传风格变换后的视频封面,如需使用本功能,请将 `generate_cover` 参数设置为 `true`。接下来需要配置的参数有 image_gen_model 和对应的 api key,请自行根据链接注册账号并且申请对应 api key,填写在对应的参数中,请注意以下模型只有你在 `image_gen_model` 参数中设置的那个模型会生效。 - +采用图生图多模态模型,自动获取视频截图并上传风格变换后的视频封面,如需使用本功能,请将 `generate_cover` 参数设置为 `true`,并且写下你自己的 prompt,注意部分模型只支持英文,接下来需要配置的参数有 image_gen_model 和对应的 api key,请自行根据链接注册账号并且申请对应 api key,填写在对应的参数中,请注意以下模型只有你在 `image_gen_model` 参数中设置的那个模型会生效。 | Company | Model Name | `image_gen_model` | `API Key` | |--------------|--------------------------------|-------------------|---------------------------------------------------------------------------------| @@ -208,10 +207,10 @@ MLLM 模型主要用于自动切片后的切片标题生成,此功能默认关 #### 3. 配置上传参数 -上传默认参数如下,[]中内容全部自动替换。可以在 `bilive.toml` 中自定义相关配置,映射关键词为 `{artist}`、`{date}`、`{title}`、`{source_link}`,可自行组合删减定制模板: +在 `bilive.toml` 中自定义相关配置,映射关键词为 `{artist}`、`{date}`、`{title}`、`{source_link}`,请自行组合删减定制模板: -- `title` 标题模板是`{artist}直播回放-{date}-{title}`,效果为"【弹幕+字幕】[XXX]直播回放-[日期]-[直播间标题]",可自行修改。 -- `description` 简介模板是`{artist}直播,直播间地址:{source_link} 内容仅供娱乐,直播中主播的言论、观点和行为均由主播本人负责,不代表录播员的观点或立场。`,效果为"【弹幕+字幕】[XXX]直播,直播间地址:[https://live.bilibili.com/XXX] 内容仅供娱乐,直播中主播的言论、观点和行为均由主播本人负责,不代表录播员的观点或立场。",可自行修改。 +- `title` 标题模板。 +- `description` 简介模板。 - `gift_price_filter = 1` 表示过滤价格低于 1 元的礼物。 - `reserve_for_fixing = false` 表示如果视频出现错误,重试失败后不保留视频用于修复,推荐硬盘空间有限的用户设置 false。 - `upload_line = "auto"` 表示自动探测上传线路并上传,如果需要指定固定的线路,可以设置为 `bldsa`、`ws`、`tx`、`qn`、`bda2`。 diff --git a/bilive.toml b/bilive.toml index cebf3c0..1516f80 100644 --- a/bilive.toml +++ b/bilive.toml @@ -15,10 +15,8 @@ inference_model = "small" # If you choose "deploy", you should download the infe [video] # You can change the title as you like, eg. -# f"{artist}直播回放-{date}-{title}" - Streamer直播回放-20250328-Live title -# f"{date}-{artist}直播回放" - 20250328-Streamer直播回放 -title = "{artist}直播回放-{date}-{title}" # Key words: {artist}, {date}, {title}, {source_link} -description = "{artist}直播回放,直播间地址:{source_link} 内容仅供娱乐,直播中主播的言论、观点和行为均由主播本人负责,不代表录播员的观点或立场。" # Key words: {artist}, {date}, {title}, {source_link} +title = "{date}直播" # Key words: {artist}, {date}, {title}, {source_link} +description = "录制请征求主播同意,若未经同意就录制,所引起的任何法律问题均由该违规录制的 b 站账号承担。" # Key words: {artist}, {date}, {title}, {source_link} gift_price_filter = 1 # The gift whose price is less than this value will be filtered, unit: RMB reserve_for_fixing = false # If encounter MOOV crash error, delete the video or reserve for fixing upload_line = "auto" # The upload line to be used, default None is auto detect(recommended), if you want to specify, it can be "bldsa", "ws", "tx", "qn", "bda2". @@ -30,13 +28,15 @@ slice_num = 2 # the number of slices slice_overlap = 30 # the overlap of slices(seconds) see my package https://github.com/timerring/auto-slice-video for more details slice_step = 1 # the step of slices(seconds) min_video_size = 200 # The minimum video size to be sliced (MB) -mllm_model = "gemini" # the multi-model LLMs, can be "gemini" or "zhipu" or "qwen" +mllm_model = "qwen" # the multi-model LLMs, can be "qwen" or "gemini" or "zhipu" +slice_prompt = "" # Write your own slice prompt here +qwen_api_key = "" # Apply for your own Qwen API key at https://bailian.console.aliyun.com/?apiKey=1 zhipu_api_key = "" # Apply for your own GLM-4v-Plus API key at https://www.bigmodel.cn/invite?icode=shBtZUfNE6FfdMH1R6NybGczbXFgPRGIalpycrEwJ28%3D gemini_api_key = "" # Apply for your own Gemini API key at https://aistudio.google.com/app/apikey -qwen_api_key = "" # Apply for your own Qwen API key at https://bailian.console.aliyun.com/?apiKey=1 [cover] generate_cover = false # whether to generate cover +cover_prompt = "" # Write your own cover prompt here image_gen_model = "minimax" # the image generation model, can be "minimax" or "siliconflow" or "tencent" or "baidu" or "stability" or "luma" or "ideogram" or "recraft" or "amazon" minimax_api_key = "" # Apply for your own Minimax API key at https://platform.minimaxi.com/user-center/basic-information/interface-key siliconflow_api_key = "" # Apply for your own SiliconFlow API key at https://cloud.siliconflow.cn/i/3Szr5BVg diff --git a/src/autoslice/mllm_sdk/gemini_new_sdk.py b/src/autoslice/mllm_sdk/gemini_new_sdk.py index 24b5052..31e0ed2 100644 --- a/src/autoslice/mllm_sdk/gemini_new_sdk.py +++ b/src/autoslice/mllm_sdk/gemini_new_sdk.py @@ -1,7 +1,7 @@ from google import genai from google.genai import types from src.log.logger import scan_log -from src.config import GEMINI_API_KEY +from src.config import GEMINI_API_KEY, SLICE_PROMPT def gemini_generate_title(video_path, artist): @@ -14,7 +14,7 @@ def gemini_generate_title(video_path, artist): model='models/gemini-2.0-flash', contents=types.Content( parts=[ - types.Part(text=f'视频是{artist}的直播的切片,请根据该视频中的内容及弹幕信息,为这段视频起一个调皮并且吸引眼球的标题,只需要返回一个标题即可,无需返回其他内容,标题中不要有表情符号。'), + types.Part(text=SLICE_PROMPT), types.Part( inline_data=types.Blob(data=video_bytes, mime_type='video/mp4') ) @@ -22,6 +22,6 @@ def gemini_generate_title(video_path, artist): ) ) scan_log.info("使用 Gemini-2.0-flash 生成切片标题") - scan_log.info(f"Prompt: 视频是{artist}的直播的切片,请根据该视频中的内容及弹幕信息,为这段视频起一个调皮并且吸引眼球的标题,只需要返回一个标题即可,无需返回其他内容,标题中不要有表情符号。") + scan_log.info(f"Prompt: {SLICE_PROMPT}") scan_log.info(f"生成的切片标题为: {response.text}") return response.text \ No newline at end of file diff --git a/src/autoslice/mllm_sdk/gemini_old_sdk.py b/src/autoslice/mllm_sdk/gemini_old_sdk.py index 604c1e1..ba204d4 100644 --- a/src/autoslice/mllm_sdk/gemini_old_sdk.py +++ b/src/autoslice/mllm_sdk/gemini_old_sdk.py @@ -1,5 +1,5 @@ import google.generativeai as genai -from src.config import GEMINI_API_KEY +from src.config import GEMINI_API_KEY, SLICE_PROMPT from src.log.logger import scan_log import time @@ -22,7 +22,7 @@ def gemini_generate_title(video_path, artist): raise ValueError(video_file.state.name) # Create the prompt. - prompt = f"视频是{artist}的直播的切片,请根据该视频中的内容及弹幕信息,为这段视频起一个调皮并且吸引眼球的标题,只返回该标题即可,无需返回其他内容,标题中不要有表情符号。" + prompt = SLICE_PROMPT # Set the model to Gemini Flash. model = genai.GenerativeModel(model_name="models/gemini-2.0-flash") @@ -32,6 +32,6 @@ def gemini_generate_title(video_path, artist): # delete the video file genai.delete_file(video_file.name) scan_log.info("使用 Gemini-2.0-flash 生成切片标题") - scan_log.info(f"Prompt: 视频是{artist}的直播的切片,请根据该视频中的内容及弹幕信息,为这段视频起一个调皮并且吸引眼球的标题,只需要返回一个标题即可,无需返回其他内容,标题中不要有表情符号。") + scan_log.info(f"Prompt: {SLICE_PROMPT}") scan_log.info(f"生成的切片标题为: {response.text}") return response.text \ No newline at end of file diff --git a/src/autoslice/mllm_sdk/qwen_sdk.py b/src/autoslice/mllm_sdk/qwen_sdk.py index a7f4c36..09f1525 100644 --- a/src/autoslice/mllm_sdk/qwen_sdk.py +++ b/src/autoslice/mllm_sdk/qwen_sdk.py @@ -1,4 +1,4 @@ -from src.config import QWEN_API_KEY +from src.config import QWEN_API_KEY, SLICE_PROMPT from src.log.logger import scan_log from openai import OpenAI import os @@ -28,12 +28,12 @@ def qwen_generate_title(video_path, artist): "type": "video_url", "video_url": {"url": f"data:video/mp4;base64,{base64_video}"}, }, - {"type": "text", "text": f"视频是{artist}的直播切片,请根据该视频中的内容及弹幕信息,为这段视频起一个调皮并且吸引眼球的标题,标题中不要表情符号,可以适当使用网络热词或流行语"}, + {"type": "text", "text": SLICE_PROMPT}, ], } ], ) scan_log.info("使用 Qwen-2.5-72B-Instruct 生成切片标题") - scan_log.info(f"Prompt: 视频是{artist}的直播切片,请根据该视频中的内容及弹幕信息,为这段视频起一个调皮并且吸引眼球的标题,标题中不要表情符号,可以适当使用网络热词或流行语") + scan_log.info(f"Prompt: {SLICE_PROMPT}") scan_log.info(f"生成的切片标题为: {completion.choices[0].message.content}") return completion.choices[0].message.content.strip('"') diff --git a/src/autoslice/mllm_sdk/zhipu_sdk.py b/src/autoslice/mllm_sdk/zhipu_sdk.py index 28c9835..ba1cf64 100644 --- a/src/autoslice/mllm_sdk/zhipu_sdk.py +++ b/src/autoslice/mllm_sdk/zhipu_sdk.py @@ -1,7 +1,7 @@ # Copyright (c) 2024 bilive. import base64 -from src.config import ZHIPU_API_KEY +from src.config import ZHIPU_API_KEY, SLICE_PROMPT from zhipuai import ZhipuAI from src.log.logger import scan_log @@ -24,13 +24,13 @@ def zhipu_glm_4v_plus_generate_title(video_path, artist): }, { "type": "text", - "text": f"视频是{artist}的直播的切片,请根据该视频中的内容及弹幕信息,为这段视频起一个调皮并且吸引眼球的标题,注意标题中如果有“主播”请替换成{artist}" + "text": SLICE_PROMPT } ] } ] ) scan_log.info("使用 Zhipu-glm-4v-plus 生成切片标题") - scan_log.info(f"Prompt: 视频是{artist}的直播的切片,请根据该视频中的内容及弹幕信息,为这段视频起一个调皮并且吸引眼球的标题,注意标题中如果有“主播”请替换成{artist}") + scan_log.info(f"Prompt: {SLICE_PROMPT}") scan_log.info(f"生成的切片标题为: {response.choices[0].message.content}") return response.choices[0].message.content.replace("《", "").replace("》", "") \ No newline at end of file diff --git a/src/config.py b/src/config.py index 2788b6d..ffce9d5 100644 --- a/src/config.py +++ b/src/config.py @@ -85,3 +85,6 @@ IDEOGRAM_API_KEY = config.get('cover', {}).get('ideogram_api_key') RECRAFT_API_KEY = config.get('cover', {}).get('recraft_api_key') AWS_ACCESS_KEY_ID = config.get('cover', {}).get('aws_access_key_id') AWS_SECRET_ACCESS_KEY = config.get('cover', {}).get('aws_secret_access_key') + +SLICE_PROMPT = config.get('slice', {}).get('slice_prompt') +COVER_PROMPT = config.get('cover', {}).get('cover_prompt') \ No newline at end of file diff --git a/src/cover/image_model_sdk/amazon_sdk.py b/src/cover/image_model_sdk/amazon_sdk.py index 4fb560a..7381fc4 100644 --- a/src/cover/image_model_sdk/amazon_sdk.py +++ b/src/cover/image_model_sdk/amazon_sdk.py @@ -12,7 +12,7 @@ from PIL import Image from botocore.exceptions import ClientError import os import time -from src.config import AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY +from src.config import AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, COVER_PROMPT class ImageError(Exception): "Custom exception for errors returned by Amazon Titan Image Generator V2" @@ -67,7 +67,7 @@ def amazon_generate_cover(your_file_path): body = json.dumps({ "taskType": "TEXT_IMAGE", "textToImageParams": { - "text": "This is a video screenshot, please generate a cover in the style of a manga", + "text": COVER_PROMPT, "negativeText": "", "conditionImage": input_image, "controlMode": "CANNY_EDGE" diff --git a/src/cover/image_model_sdk/baidu_sdk.py b/src/cover/image_model_sdk/baidu_sdk.py index 3728a00..9de52a3 100644 --- a/src/cover/image_model_sdk/baidu_sdk.py +++ b/src/cover/image_model_sdk/baidu_sdk.py @@ -6,7 +6,7 @@ from PIL import Image from io import BytesIO import time from src.upload.bilitool.bilitool.model.model import Model -from src.config import BAIDU_API_KEY +from src.config import BAIDU_API_KEY, COVER_PROMPT def cover_up(img: str): @@ -72,7 +72,7 @@ def baidu_generate_cover(your_file_path): payload = json.dumps( { "model": "irag-1.0", - "prompt": "这是视频截图,请根据该图生成对应的动漫类型的封面", + "prompt": COVER_PROMPT, "refer_image": cover_url, } ) diff --git a/src/cover/image_model_sdk/ideogram_sdk.py b/src/cover/image_model_sdk/ideogram_sdk.py index 28bafae..58d20bf 100644 --- a/src/cover/image_model_sdk/ideogram_sdk.py +++ b/src/cover/image_model_sdk/ideogram_sdk.py @@ -2,7 +2,7 @@ import requests import json import os import time -from src.config import IDEOGRAM_API_KEY +from src.config import IDEOGRAM_API_KEY, COVER_PROMPT def ideogram_generate_cover(your_file_path): @@ -19,7 +19,7 @@ def ideogram_generate_cover(your_file_path): payload = { "image_request": json.dumps( { - "prompt": "This is a video screenshot, please generate a cover in the style of a manga", + "prompt": COVER_PROMPT, "aspect_ratio": "ASPECT_10_16", "image_weight": 75, "magic_prompt_option": "ON", diff --git a/src/cover/image_model_sdk/kolors_sdk.py b/src/cover/image_model_sdk/kolors_sdk.py index 1e590ef..6ba2fef 100644 --- a/src/cover/image_model_sdk/kolors_sdk.py +++ b/src/cover/image_model_sdk/kolors_sdk.py @@ -2,7 +2,7 @@ import requests import base64 import time import os -from src.config import SILICONFLOW_API_KEY +from src.config import SILICONFLOW_API_KEY, COVER_PROMPT def kolors_generate_cover(your_file_path): @@ -17,7 +17,7 @@ def kolors_generate_cover(your_file_path): payload = { "model": "Kwai-Kolors/Kolors", - "prompt": "这是一个视频截图,请尝试生成对应的日本动漫类型的封面", + "prompt": COVER_PROMPT, "image_size": "1024x1024", "batch_size": 1, "num_inference_steps": 20, diff --git a/src/cover/image_model_sdk/luma_sdk.py b/src/cover/image_model_sdk/luma_sdk.py index aa20567..0152567 100644 --- a/src/cover/image_model_sdk/luma_sdk.py +++ b/src/cover/image_model_sdk/luma_sdk.py @@ -7,7 +7,7 @@ from PIL import Image from io import BytesIO from lumaai import LumaAI from src.upload.bilitool.bilitool.model.model import Model -from src.config import LUMA_API_KEY +from src.config import LUMA_API_KEY, COVER_PROMPT def cover_up(img: str): @@ -72,7 +72,7 @@ def luma_generate_cover(your_file_path): auth_token=LUMA_API_KEY, ) generation = client.generations.image.create( - prompt="This is a video screenshot, please generate a cover in the style of a manga", + prompt=COVER_PROMPT, image_ref=[{"url": cover_url, "weight": 0.85}], ) completed = False diff --git a/src/cover/image_model_sdk/minimax_sdk.py b/src/cover/image_model_sdk/minimax_sdk.py index 1db6438..5017422 100644 --- a/src/cover/image_model_sdk/minimax_sdk.py +++ b/src/cover/image_model_sdk/minimax_sdk.py @@ -3,7 +3,7 @@ import json import base64 import os import time -from src.config import MINIMAX_API_KEY +from src.config import MINIMAX_API_KEY, COVER_PROMPT def minimax_generate_cover(your_file_path): @@ -20,7 +20,7 @@ def minimax_generate_cover(your_file_path): payload = json.dumps( { "model": "image-01", - "prompt": "这是一个视频截图,请生成其对应的吉普力风格的图片", + "prompt": COVER_PROMPT, "subject_reference": [ {"type": "character", "image_file": f"data:image/jpeg;base64,{data}"} ], diff --git a/src/cover/image_model_sdk/recraft_sdk.py b/src/cover/image_model_sdk/recraft_sdk.py index 04663ed..7c06443 100644 --- a/src/cover/image_model_sdk/recraft_sdk.py +++ b/src/cover/image_model_sdk/recraft_sdk.py @@ -1,5 +1,5 @@ from openai import OpenAI -from src.config import RECRAFT_API_KEY +from src.config import RECRAFT_API_KEY, COVER_PROMPT import requests import os import time @@ -20,7 +20,7 @@ def recraft_generate_cover(your_file_path): 'image': open(your_file_path, 'rb'), }, body={ - 'prompt': 'This is a video screenshot, please generate a cover in the style of a manga', + 'prompt': COVER_PROMPT, 'strength': 0.75, }, ) diff --git a/src/cover/image_model_sdk/stability_sdk.py b/src/cover/image_model_sdk/stability_sdk.py index c724209..9e907d1 100644 --- a/src/cover/image_model_sdk/stability_sdk.py +++ b/src/cover/image_model_sdk/stability_sdk.py @@ -1,5 +1,5 @@ import requests -from src.config import STABILITY_API_KEY +from src.config import STABILITY_API_KEY, COVER_PROMPT import time import os @@ -24,7 +24,7 @@ def stable_diffusion_generate_cover(your_file_path): }, files={"image": ("image.jpg", img_file, "image/jpeg")}, data={ - "prompt": "This is a video screenshot, please generate a cover in the style of a manga", # English only + "prompt": COVER_PROMPT, # English only "strength": 0.75, "output_format": "jpeg", "mode": "image-to-image", diff --git a/src/cover/image_model_sdk/tencent_sdk.py b/src/cover/image_model_sdk/tencent_sdk.py index 63fefa2..f780ec9 100644 --- a/src/cover/image_model_sdk/tencent_sdk.py +++ b/src/cover/image_model_sdk/tencent_sdk.py @@ -8,7 +8,7 @@ import base64 from datetime import datetime import os import requests -from src.config import TENCENT_SECRET_ID, TENCENT_SECRET_KEY +from src.config import TENCENT_SECRET_ID, TENCENT_SECRET_KEY, COVER_PROMPT if sys.version_info[0] <= 2: from httplib import HTTPSConnection @@ -125,7 +125,7 @@ def hunyuan_generate_cover(your_file_path): submit_action = "SubmitHunyuanImageJob" with open(your_file_path, "rb") as image_file: data = base64.b64encode(image_file.read()).decode("utf-8") - payload = f'{{"Prompt":"这是一个视频截图,请尝试根据该图生成对应的动漫类型的封面","Style":"riman","ContentImage":{{"ImageBase64":"data:image/png;base64,{data}"}}}}' + payload = f'{{"Prompt":"{COVER_PROMPT}","Style":"riman","ContentImage":{{"ImageBase64":"data:image/png;base64,{data}"}}}}' submit_return = post_request(submit_action, payload).decode("utf-8") job_id = json.loads(submit_return)["Response"]["JobId"] query_action = "QueryHunyuanImageJob" diff --git a/src/upload/extract_video_info.py b/src/upload/extract_video_info.py index 444b2c9..d85f236 100644 --- a/src/upload/extract_video_info.py +++ b/src/upload/extract_video_info.py @@ -46,17 +46,15 @@ def generate_title(video_path): upload_log.error(f"Error occurred in generate_title: {title}") return None source_link = generate_source(video_path) - prefix = "【弹幕+字幕】" formatted_title = TITLE.format(artist=artist, date=date, title=title, source_link=source_link) - new_title = f"{prefix}{formatted_title}" + new_title = f"{formatted_title}" return new_title def generate_desc(video_path): title, artist, date = get_video_info(video_path) source_link = generate_source(video_path) - prefix = "【弹幕+字幕】" formatted_desc = DESC.format(artist=artist, date=date, title=title, source_link=source_link) - new_desc = f"{prefix}{formatted_desc}" + new_desc = f"{formatted_desc}" return new_desc def generate_tag(video_path):