Skip to content
This repository was archived by the owner on Jan 28, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ upload.yaml
dist/
*.spec
build/
biliupload/config.json
test.py
38 changes: 7 additions & 31 deletions biliupload/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import sys
import os
import logging
from biliupload.ioer import ioer
from biliupload.login.login_bili import login_bili
from biliupload.utils.parse_cookies import parse_cookies
from biliupload.upload.bili_upload import BiliUploader
from biliupload.utils.parse_yaml import parse_yaml
from biliupload.controller.upload_controller import UploadController

def cli():
logging.basicConfig(
Expand All @@ -20,12 +22,12 @@ def cli():
subparsers = parser.add_subparsers(dest='subcommand', help='Subcommands')

# Login subcommand
subparsers.add_parser('login', help='login and save the cookies')
login_parser = subparsers.add_parser('login', help='login and save the cookie')
login_parser.add_argument('--export', action='store_true', help='(default is false) export the login cookie file')

# Upload subcommand
upload_parser = subparsers.add_parser('upload', help='upload the video')
upload_parser.add_argument('video_path', help='(required) the path to video file')
upload_parser.add_argument('-c', '--cookies', required=True, help='The path to cookies')
upload_parser.add_argument('-y', '--yaml', help='The path to yaml file(if yaml file is provided, the arguments below will be ignored)')
upload_parser.add_argument('--copyright', type=int, default=2, help='(default is 2) 1 for original, 2 for reprint')
upload_parser.add_argument('--title', help='(default is video name) The title of video')
Expand All @@ -43,37 +45,11 @@ def cli():
sys.exit()

if args.subcommand == 'login':
login_bili()
login_bili(args.export)

if args.subcommand == 'upload':
sessdata, bili_jct = parse_cookies(args.cookies)
if (args.yaml):
line, copyright, tid, title, desc, tags = parse_yaml(args.yaml)
BiliUploader(
sessdata,
bili_jct,
line
).upload_and_publish_video(
args.video_path,
title=title,
desc=desc,
copyright=copyright,
tid=tid,
tags=tags
)
else:
BiliUploader(
sessdata,
bili_jct,
args.line
).upload_and_publish_video(
args.video_path,
title=args.title,
desc=args.desc,
copyright=args.copyright,
tid=args.tid,
tags=args.tags
)
upload_controller = UploadController()
upload_controller.upload_and_publish_video(args.video_path)

if __name__ == '__main__':
cli()
25 changes: 25 additions & 0 deletions biliupload/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
},
"cookies": {
"SESSDATA": "",
"bili_jct": ""
},
"upload": {
"copyright": 2,
"title": "",
"desc": "",
"tid": 138,
"tags": "biliupload",
"line": "bda2",
"source": "\u6765\u6e90\u4e8e\u4e92\u8054\u7f51",
"cover": "",
"dynamic": ""
},
"download": {
"dm": 1,
"qn": 999,
"chunk_size": 1024
}
}
Empty file.
66 changes: 66 additions & 0 deletions biliupload/controller/upload_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright (c) 2025 biliupload

from biliupload.ioer import ioer
from biliupload.upload.bili_upload import BiliUploader
from pathlib import Path
import re
from math import ceil
import logging


class UploadController:
def __init__(self):
self.ioer = ioer()
self.logger = logging.getLogger('biliupload')
self.config = self.ioer.get_config()
self.bili_uploader = BiliUploader(self.config, self.logger)

def upload_and_publish_video(self, file):
"""upload and publish video on bilibili"""
file = Path(file)
assert file.exists(), f'The file {file} does not exist'
filename = file.name
title = self.config["upload"]["title"] or file.stem
filesize = file.stat().st_size
self.logger.info(f'The {title} to be uploaded')

# upload video
self.logger.info('Start preuploading the video')
pre_upload_response = self.bili_uploader.preupload(filename=filename, filesize=filesize)
upos_uri = pre_upload_response['upos_uri'].split('//')[-1]
auth = pre_upload_response['auth']
biz_id = pre_upload_response['biz_id']
chunk_size = pre_upload_response['chunk_size']
chunks = ceil(filesize/chunk_size)

self.logger.info('Start uploading the video')
upload_video_id_response = self.bili_uploader.get_upload_video_id(upos_uri=upos_uri, auth=auth)
upload_id = upload_video_id_response['upload_id']
key = upload_video_id_response['key']

bilibili_filename = re.search(r'/(.*)\.', key).group(1)

self.logger.info(f'Uploading the video in {chunks} batches')
fileio = file.open(mode='rb')
self.bili_uploader.upload_video_in_chunks(
upos_uri=upos_uri,
auth=auth,
upload_id=upload_id,
fileio=fileio,
filesize=filesize,
chunk_size=chunk_size,
chunks=chunks
)
fileio.close()

# notify the all chunks have been uploaded
self.bili_uploader.finish_upload(upos_uri=upos_uri, auth=auth, filename=filename,
upload_id=upload_id, biz_id=biz_id, chunks=chunks)

# publish video
publish_video_response = self.bili_uploader.publish_video(bilibili_filename=bilibili_filename)
bvid = publish_video_response['data']['bvid']
# print(publish_video_response)
self.logger.info(f'[{title}]upload success!\tbvid:{bvid}')
# reset the video title
self.ioer.update_specific_config("upload", "title", "")
67 changes: 67 additions & 0 deletions biliupload/ioer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Copyright (c) 2025 biliupload

import json
import os


class ioer:
def __init__(self, path=None) -> None:
if path is None:
self.path = os.path.join(os.path.dirname(__file__), "config.json")
else:
self.path = path
self.default_config = {
"headers": {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
},
"cookies": {
"SESSDATA": "",
"bili_jct": ""
},
"upload": {
"copyright": 2,
"title": "",
"desc": "",
"tid": 138,
"tags": "biliupload",
"line": "bda2",
"source": "\u6765\u6e90\u4e8e\u4e92\u8054\u7f51",
"cover": "",
"dynamic": ""
},
"download": {
"dm": 1,
"qn": 999,
"chunk_size": 1024
}
}

def get_default_config(self):
return self.default_config

def reset_config(self):
self.write(self.default_config)

def save_cookies_info(self, sessdata, bili_jct):
config_info = self.get_config()
config_info['cookies']['SESSDATA'] = sessdata
config_info['cookies']['bili_jct'] = bili_jct
self.write(config_info)

def update_specific_config(self, action, key, value):
curr_config = self.get_config()
curr_config[action][key] = value
self.write(curr_config)

def get_config(self):
if not os.path.exists(self.path):
self.reset_config()
return self.read()

def read(self):
with open(self.path, "r") as f:
return json.load(f)

def write(self, config):
with open(self.path, "w") as f:
json.dump(config, f, indent=4)
20 changes: 13 additions & 7 deletions biliupload/login/login_bili.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import json
import qrcode
from urllib.parse import urlencode
from biliupload.ioer import ioer

APP_KEY = "4409e2ce8ffd12b8"
APP_SEC = "59b43e04ad6965f34319062b478f83dd"
Expand Down Expand Up @@ -45,7 +46,7 @@ def get_tv_qrcode_url_and_auth_code():
else:
raise Exception("get_tv_qrcode_url_and_auth_code error")

def verify_login(auth_code):
def verify_login(auth_code, export):
api = "https://passport.bilibili.com/x/passport-tv-login/qrcode/poll"
data = {
"auth_code": auth_code,
Expand All @@ -56,23 +57,28 @@ def verify_login(auth_code):
while True:
body = execute_curl_command(api, data)
if body['code'] == 0:
print("Login success!")
filename = "cookie.json"
with open(filename, 'w', encoding='utf-8') as f:
json.dump(body, f, ensure_ascii=False, indent=4)
print(f"cookie has been saved to {filename}")
if export:
with open(filename, 'w', encoding='utf-8') as f:
json.dump(body, f, ensure_ascii=False, indent=4)
print(f"cookie has been saved to {filename}")

sessdata_value = body['data']['cookie_info']['cookies'][0]['value']
bili_jct_value = body['data']['cookie_info']['cookies'][1]['value']
ioer().save_cookies_info(sessdata_value, bili_jct_value)
print("Login success!")
break
else:
time.sleep(3)

def login_bili():
def login_bili(export):
input("Please maximize the window to ensure the QR code is fully displayed, press Enter to continue: ")
login_url, auth_code = get_tv_qrcode_url_and_auth_code()
qr = qrcode.QRCode()
qr.add_data(login_url)
qr.print_ascii()
print("Or copy this link to your phone Bilibili:", login_url)
verify_login(auth_code)
verify_login(auth_code, export)

if __name__ == "__main__":
login_bili()
Loading