Skip to content

Commit

Permalink
upload for tiktok
Browse files Browse the repository at this point in the history
  • Loading branch information
pookz@stme committed Apr 10, 2024
1 parent f5f9e7d commit de17838
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 21 deletions.
81 changes: 60 additions & 21 deletions README.MD
Original file line number Diff line number Diff line change
@@ -1,51 +1,60 @@
# social-auto-upload
social-auto-upload 该项目旨在自动化发布视频到各个社交媒体平台

social-auto-upload This project aims to automate the posting of videos to various social media platforms.

<img src="media/show/tkupload.gif" alt="tiktok show" width="800"/>

## 💡Feature
- 中国主流社交媒体平台:
- 抖音
- 视频号
- bilibili
- 小红书
- 快手(todo)
- [x] 抖音
- [x] 视频号
- [x] bilibili
- [x] 小红书
- [ ] 快手

- 部分国外社交媒体:
- tiktok(todo)
- youtube(todo)
- 自动化上传(schedule)(todo)
- 定时上传(cron)
- cookie 管理(todo)
- 国外平台proxy 设置(todo)
- 多线程上传(todo)
- slack 推送(todo)
- [x] tiktok
- [ ] youtube
- [ ] 自动化上传(schedule)
- [x] 定时上传(cron)
- [ ] cookie 管理
- [ ] 国外平台proxy 设置
- [ ] 多线程上传
- [ ] slack 推送


# 💾Installation
```
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
playwright install chromium
pip install -r requirements.txt
playwright install chromium firefox
```

# 🐇 About
The project for my own project extracted, my release strategy is timed release (released a day in advance), so the release part of the event are used for the next day time!

If you need to release it immediately, you can study the source code or ask me questions.

该项目为我自用项目抽离出来,我的发布策略是定时发布(提前一天发布),故发布部分采用的事件均为第二天的时间

如果你有需求立即发布,可自行研究源码或者向我提问


# 核心模块解释

### 1. 视频文件准备
filepath 本地视频目录,目录包含
- 视频文件
- 视频meta信息txt文件
### 1. 视频文件准备(video prepare)
filepath 本地视频目录,目录包含(filepath Local video directory containing)

- 视频文件(video files)
- 视频meta信息txt文件(video meta information txt file)

举例(for example):

举例:
file:2023-08-24_16-29-52 - 这位勇敢的男子为了心爱之人每天坚守 .mp4

meta_file:2023-08-24_16-29-52 - 这位勇敢的男子为了心爱之人每天坚守 .txt

meta_file 内容:
meta_file 内容(content)
```angular2html
这位勇敢的男子为了心爱之人每天坚守 🥺❤️‍🩹
#坚持不懈 #爱情执着 #奋斗使者 #短视频
Expand Down Expand Up @@ -166,12 +175,42 @@ bilibili cookie 长期有效(至少我运行2年以来是这样的)

---

### 6. tiktok
使用playwright模拟浏览器行为(Simulating Browser Behavior with playwright)
1. 准备视频目录结构(Prepare the video directory structure)
2. cookie获取(generate your cookie):get_tk_cookie.py
![get tiktok cookie](media/tk_login.png)
3. 上传视频(upload video):upload_video_to_tiktok.py



其他部分解释:
```
参考上面douyin_setup 配置
```

other part explain(for eng friends):
```
tiktok_setup handle parameter is True to get cookie manually False to check cookie validity
generate_schedule_time_next_day defaults to start on the next day (this is to avoid accidental time selection errors)
Parameter explanation:
- total_videos Number of videos uploaded this time
- videos_per_day Number of videos uploaded per day
- daily_times The video posting times are 6, 11, 14, 16, 22 by default.
- start_days Starts on the nth day.
```

---

### 其余部分(todo)
整理后上传

---

## 🐾Communicate
[donate as u like](https://www.buymeacoffee.com/hysn2001m)

探讨自动化上传、自动制作视频
|![Nas](media/mp.jpg)|![赞赏](media/QR.png)|
|:-:|:-:|
Expand Down
9 changes: 9 additions & 0 deletions examples/get_tk_cookie.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import asyncio
from pathlib import Path

from conf import BASE_DIR
from tk_uploader.main import tiktok_setup

if __name__ == '__main__':
account_file = Path(BASE_DIR / "tk_uploader" / "account.json")
cookie_setup = asyncio.run(tiktok_setup(str(account_file), handle=True))
24 changes: 24 additions & 0 deletions examples/upload_video_to_tiktok.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import asyncio
from pathlib import Path

from conf import BASE_DIR
from tk_uploader.main import tiktok_setup, TiktokVideo
from utils.files_times import generate_schedule_time_next_day, get_title_and_hashtags


if __name__ == '__main__':
filepath = Path(BASE_DIR) / "videos"
account_file = Path(BASE_DIR / "tk_uploader" / "account.json")
folder_path = Path(filepath)
# get video files from folder
files = list(folder_path.glob("*.mp4"))
file_num = len(files)
publish_datetimes = generate_schedule_time_next_day(file_num, 1, daily_times=[16])
cookie_setup = asyncio.run(tiktok_setup(account_file, handle=True))
for index, file in enumerate(files):
title, tags = get_title_and_hashtags(str(file))
print(f"video_file_name:{file}")
print(f"video_title:{title}")
print(f"video_hashtag:{tags}")
app = TiktokVideo(title, file, tags, publish_datetimes[index], account_file)
asyncio.run(app.main(), debug=False)
Binary file added media/show/tkupload.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/tk_login.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
229 changes: 229 additions & 0 deletions tk_uploader/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
# -*- coding: utf-8 -*-
import re
from datetime import datetime

from playwright.async_api import Playwright, async_playwright
import time
import os
import asyncio
from tk_uploader.tk_config import Tk_Locator
from utils.files_times import get_absolute_path


async def cookie_auth(account_file):
async with async_playwright() as playwright:
browser = await playwright.firefox.launch(headless=True)
context = await browser.new_context(storage_state=account_file)
# 创建一个新的页面
page = await context.new_page()
# 访问指定的 URL
await page.goto("https://www.tiktok.com/creator-center/content")
await page.wait_for_load_state('networkidle')
try:
# 选择所有的 select 元素
select_elements = await page.query_selector_all('select')
for element in select_elements:
class_name = await element.get_attribute('class')
# 使用正则表达式匹配特定模式的 class 名称
if re.match(r'tiktok-.*-SelectFormContainer.*', class_name):
print("[+] cookie expired")
return False
print("[+] cookie valid")
return True
except:
print("[+] cookie valid")
return True


async def tiktok_setup(account_file, handle=False):
account_file = get_absolute_path(account_file, "tk_uploader")
if not os.path.exists(account_file) or not await cookie_auth(account_file):
if not handle:
return False
print('[+] cookie file is not existed or expired. Now open the browser auto. Please login with your way(gmail phone, whatever, the cookie file will generated after login')
await get_tiktok_cookie(account_file)
return True


async def get_tiktok_cookie(account_file):
async with async_playwright() as playwright:
options = {
'args': [
'--lang en-GB',
],
'headless': False, # Set headless option here
}
# Make sure to run headed.
browser = await playwright.firefox.launch(**options)
# Setup context however you like.
context = await browser.new_context() # Pass any options
# Pause the page, and start recording manually.
page = await context.new_page()
await page.goto("https://www.tiktok.com/creator-center/content")
await page.pause()
# 点击调试器的继续,保存cookie
await context.storage_state(path=account_file)


class TiktokVideo(object):
def __init__(self, title, file_path, tags, publish_date: datetime, account_file):
self.title = title
self.file_path = file_path
self.tags = tags
self.publish_date = publish_date
self.account_file = account_file

async def set_schedule_time(self, page, publish_date):
print("click schedule")

await page.frame_locator(Tk_Locator.tk_iframe).locator('div.scheduled-container input').click()
scheduled_picker = page.frame_locator(Tk_Locator.tk_iframe).locator('div.scheduled-picker')
await scheduled_picker.locator('div.date-picker-input').click()

calendar_month = await page.frame_locator(Tk_Locator.tk_iframe).locator('div.calendar-wrapper span.month-title').inner_text()

n_calendar_month = datetime.strptime(calendar_month, '%B').month

schedule_month = publish_date.month

if n_calendar_month != schedule_month:
if n_calendar_month < schedule_month:
arrow = page.frame_locator(Tk_Locator.tk_iframe).locator('div.calendar-wrapper span.arrow').nth(-1)
else:
arrow = page.frame_locator(Tk_Locator.tk_iframe).locator('div.calendar-wrapper span.arrow').nth(0)
await arrow.click()

# day set
valid_days_locator = page.frame_locator(Tk_Locator.tk_iframe).locator(
'div.calendar-wrapper span.day.valid')
valid_days = await valid_days_locator.count()
for i in range(valid_days):
day_element = valid_days_locator.nth(i)
text = await day_element.inner_text()
if text.strip() == str(publish_date.day):
await day_element.click()
break
# time set
await page.frame_locator(Tk_Locator.tk_iframe).locator("div.time-picker-input").click()

hour_str = publish_date.strftime("%H")
correct_minute = int(publish_date.minute / 5)
minute_str = f"{correct_minute:02d}"

hour_selector = f"span.tiktok-timepicker-left:has-text('{hour_str}')"
minute_selector = f"span.tiktok-timepicker-right:has-text('{minute_str}')"

# pick hour first
await page.frame_locator(Tk_Locator.tk_iframe).locator(hour_selector).click()
# pick minutes after
await page.frame_locator(Tk_Locator.tk_iframe).locator(minute_selector).click()

# click title to remove the focus.
await page.frame_locator(Tk_Locator.tk_iframe).locator("h1:has-text('Upload video')").click()

async def handle_upload_error(self, page):
print("video upload error retrying.")
select_file_button = page.frame_locator(Tk_Locator.tk_iframe).locator('button[aria-label="Select file"]')
async with page.expect_file_chooser() as fc_info:
await select_file_button.click()
file_chooser = await fc_info.value
await file_chooser.set_files(self.file_path)

async def upload(self, playwright: Playwright) -> None:
browser = await playwright.firefox.launch(headless=False)
context = await browser.new_context(storage_state=f"{self.account_file}")

page = await context.new_page()

await page.goto("https://www.tiktok.com/creator-center/upload")
print('[+]Uploading-------{}.mp4'.format(self.title))

await page.wait_for_url("https://www.tiktok.com/creator-center/upload")
await page.wait_for_selector('iframe[data-tt="Upload_index_iframe"]')
upload_button = page.frame_locator(Tk_Locator.tk_iframe).locator(
'button:has-text("Select file"):visible')

async with page.expect_file_chooser() as fc_info:
await upload_button.click()
file_chooser = await fc_info.value
await file_chooser.set_files(self.file_path)

await self.add_title_tags(page)
# detact upload status
await self.detact_upload_status(page)
if self.publish_date != 0:
await self.set_schedule_time(page, self.publish_date)

await self.click_publish(page)

await context.storage_state(path=f"{self.account_file}") # save cookie
print(' [-] update cookie!')
await asyncio.sleep(2) # close delay for look the video status
# close all
await context.close()
await browser.close()

async def add_title_tags(self, page):

await page.frame_locator(Tk_Locator.tk_iframe).locator(
'div.public-DraftEditor-content').click()
time.sleep(2)
await page.keyboard.press("Control+KeyA")
time.sleep(2)
await page.keyboard.press("Delete")

# title part
await page.keyboard.type(self.title)
await page.keyboard.press("Enter")

# tag part
for index, tag in enumerate(self.tags, start=1):
print("Setting the %s tag" % index)
await page.keyboard.type("#" + tag, delay=20)
await asyncio.sleep(1)
await page.keyboard.press("Space")
# if await page.frame_locator(Tk_Locator.tk_iframe).locator('div.mentionSuggestions').count():
# await page.frame_locator(Tk_Locator.tk_iframe).locator('div.mentionSuggestions- > div').nth(0).click()

print(f"success add hashtag: {len(self.tags)}")

async def click_publish(self, page):
while True:
try:
publish_button = page.frame_locator(Tk_Locator.tk_iframe).locator('div.btn-post')
if await publish_button.count():
await publish_button.click()

await page.frame_locator(Tk_Locator.tk_iframe).locator("div.uploaded-modal:visible").wait_for(state="visible", timeout=1500)
print(" [-] video published success")
break
except Exception as e:
if await page.frame_locator(Tk_Locator.tk_iframe).locator("div.uploaded-modal:visible").count():
print(" [-]video published success")
break
else:
print(f" [-] Exception: {e}")
print(" [-] video publishing")
await page.screenshot(full_page=True)
await asyncio.sleep(0.5)

async def detact_upload_status(self, page):
while True:
try:
if await page.frame_locator(Tk_Locator.tk_iframe).locator('div.btn-post > button').get_attribute("disabled") is None:
print(" [-]video uploaded.")
break
else:
print(" [-] video uploading...")
await asyncio.sleep(2)
if await page.frame_locator(Tk_Locator.tk_iframe).locator('button[aria-label="Select file"]').count():
print(" [-] found some error while uploading now retry...")
await self.handle_upload_error(page)
except:
print(" [-] video uploading...")
await asyncio.sleep(2)

async def main(self):
async with async_playwright() as playwright:
await self.upload(playwright)

3 changes: 3 additions & 0 deletions tk_uploader/tk_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

class Tk_Locator(object):
tk_iframe = '[data-tt="Upload_index_iframe"]'

0 comments on commit de17838

Please sign in to comment.