Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ultrazg committed Jan 14, 2024
0 parents commit 43e01f4
Show file tree
Hide file tree
Showing 18 changed files with 1,061 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea
.vscode
build
.DS_Store
26 changes: 26 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
FROM golang:1.20 as builder

WORKDIR /app

COPY go.mod go.sum ./

RUN go mod download

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o violet .

FROM alpine

RUN apk update \
&& apk upgrade \
&& apk add --no-cache ca-certificates tzdata \
&& update-ca-certificates 2>/dev/null || true

RUN apk add --no-cache ffmpeg

COPY --from=builder /app/violet /violet

EXPOSE 8080

CMD ["/violet"]
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## violet

A Video translated text example in Go
152 changes: 152 additions & 0 deletions api/douyin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package api

import (
"encoding/json"
"fmt"
"io"
"log"
"math/rand"
"net/http"
"regexp"
"strconv"
"strings"
"time"
"violet/config"
"violet/model"
)

// randomUserAgent 从配置文件中随机返回一个 User-Agent 字符串
func randomUserAgent() string {
rand.Seed(time.Now().UnixNano())
return config.Config.App.UserAgents[rand.Intn(len(config.Config.App.UserAgents))]
}

// ProcessUserInput 正则表达式处理输入的字符
func ProcessUserInput(input string) string {
linkRegex := regexp.MustCompile(`v\.douyin\.com\/[a-zA-Z0-9]+`)
idRegex := regexp.MustCompile(`\d{19}`)

if linkRegex.MatchString(input) {
return linkRegex.FindString(input)
} else if idRegex.MatchString(input) {
return idRegex.FindString(input)
}

return ""
}

// ExtractVideoId 提取视频的 id
func ExtractVideoId(link string) string {
// 先判断链接是否包含协议,如果缺失则补充
if !strings.HasPrefix(link, "http://") && !strings.HasPrefix(link, "https://") {
link = "https://" + link
}

// 发送请求,获取重定向后的 url
resp, err := http.Get(link)
if err != nil {
log.Println("[Error] - 请求失败:", err)

return ""
}

defer resp.Body.Close()

finalURL := resp.Request.URL.String()
log.Print("[Info] - finalURL: " + finalURL)
finalURL = resp.Request.URL.String()

idRegex := regexp.MustCompile(`/video/(\d+)`)
matches := idRegex.FindStringSubmatch(finalURL)

if len(matches) > 1 {
log.Println("[Info] - 已获取到视频 ID:" + matches[1])

return matches[1]
}
return ""
}

func IsNumber(s string) bool {
_, err := strconv.Atoi(s)

return err == nil
}

// GetDouYinVideoInfo 获取抖音视频的信息
func GetDouYinVideoInfo(videoId string) (string, string, error) {
url := fmt.Sprintf("https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=%s&a_bogus=64745b2b5bdc4e75b720a9a85b19867a", videoId)
method := "GET"

client := &http.Client{}
request, err := http.NewRequest(method, url, nil)
if err != nil {
log.Println("[Error] - ", err)

return "", "", err
}

request.Header.Add("User-Agent", randomUserAgent())
response, err := client.Do(request)
if err != nil {
log.Println("[Error] - ", err)

return "", "", err
}

defer response.Body.Close()

body, err := io.ReadAll(response.Body)
if err != nil {
log.Println("[Error] - ", err)

return "", "", err
}

if response.StatusCode != 200 {
log.Println("[Error] - ", string(body))

return "", "", fmt.Errorf("[Error] - 响应失败")
}

var videoInfo model.DYVideoInfo
err = json.Unmarshal(body, &videoInfo)
if err != nil {
log.Println("[Error] - JSON 解析失败", err)

return "", "", err
}

if len(videoInfo.ItemList) > 0 && videoInfo.ItemList[0].Video.PlayAddr.Uri != "" {
uri := videoInfo.ItemList[0].Video.PlayAddr.Uri
desc := videoInfo.ItemList[0].Desc
finalURL := fmt.Sprintf("https://www.iesdouyin.com/aweme/v1/play/?video_id=%s&ratio=1080p&line=0", uri)

return finalURL, desc, nil
}

return "", "", fmt.Errorf("[Error] - 找不到视频")
}

func GetDouYinInfo(link string) (string, string, error) {
videoIdOrLink := ProcessUserInput(link)
var videoId string
if videoIdOrLink != "" {
if IsNumber(videoIdOrLink) {
videoId = videoIdOrLink
} else {
videoId = ExtractVideoId(videoIdOrLink)
}
}

if len(videoId) == 0 {
return "", "", fmt.Errorf("[Error] - 找不到视频")
}

finalURL, title, err := GetDouYinVideoInfo(videoId)
if err != nil {
return "", "", err
}

return finalURL, title, nil
}
195 changes: 195 additions & 0 deletions api/gemini.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package api

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"net/url"
"time"
"violet/config"
"violet/model"
)

func RandKey(t []string) string {
rand.Seed(time.Now().UnixNano())

return t[rand.Intn(len(t))]
}

func NewClient() *http.Client {
// 开启代理
if config.Config.Proxy.Protocol != "" {
proxyURL, err := url.Parse(config.Config.Proxy.Protocol)
if err != nil {
log.Println("[Error] - 设置代理出错:", err)
log.Println("用默认直连")
client := &http.Client{}

return client
}

log.Println("[Info] - 正在代理请求 Gemini\n代理地址:", proxyURL)

// 创建一个自定义的 Transport
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}

// 使用自定义的 Transport 创建一个 http.Client
client := &http.Client{
Transport: transport,
}

return client
} else {
// 全部直连,不走代理
client := &http.Client{}

return client
}
}

func SetGeminiV(data model.FrameInfo) (error, string) {
if data.Base64Data == "" {
return fmt.Errorf("[Error] - Base64 数据为空"), ""
}

_url := fmt.Sprintf(config.Config.App.GeminiUrl+"/v1beta/models/gemini-pro-vision:generateContent?key=%s", RandKey(config.Config.App.GeminiKey))
method := "POST"
payload := model.GeminiData{
Contents: []model.Contents{
{
Parts: []model.Parts{
{
Text: fmt.Sprintf("这个图片是一段视频中第%d段的第%d帧,他的详细内容内容是什么?比如有什么人物,他们在做什么动作,说什么话。这个时候你就是一个视频脚本分析大师,你应该剖析他们原本的剧情或者画面呈现的东西,你应该直接输出告诉我视频这一帧呈现的内容,现在请开始你分析:", data.SegmentIndex, data.FrameIndex),
},
{
InlineData: &model.InlineData{
MimeType: "image/jpeg",
Data: data.Base64Data,
},
},
},
},
},
}

payloadBytes, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("[Error] - error marshaling payload: %v", err), ""
}

client := NewClient()

request, err := http.NewRequest(method, _url, bytes.NewReader(payloadBytes))
if err != nil {
return fmt.Errorf("[Error] - 创建请求失败:%v", err), ""
}

request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
if err != nil {
return fmt.Errorf("[Error] - 发送请求失败:%v", err), ""
}

defer response.Body.Close()

body, err := ioutil.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("[Error] - 读取响应失败:%v", err), ""
}

if response.StatusCode != 200 {
return fmt.Errorf("[Error] - status code error: %d %s", response.StatusCode, string(body)), ""
}

// 解析响应
var geminiResponse model.GeminiResponse
err = json.Unmarshal(body, &geminiResponse)
if err != nil {
return fmt.Errorf("[Error] - JSON 解析失败 %v", err), ""
}

// 检查 Candidates 切片是否为空
if len(geminiResponse.Candidates) == 0 {
return fmt.Errorf("[Error] - Candidates 为空"), ""
}

// 检查 Parts 切片是否为空
if len(geminiResponse.Candidates[0].Content.Parts) == 0 {
return fmt.Errorf("[Error] - Parts 为空"), ""
}

frameDescription := fmt.Sprintf("片段 %d 中的第 %d 帧的内容是%s",
data.SegmentIndex,
data.FrameIndex,
geminiResponse.Candidates[0].Content.Parts[0].Text)

return nil, frameDescription
}

func SetGemini(content string) (error, string) {
_url := fmt.Sprintf(config.Config.App.GeminiUrl+"/v1beta/models/gemini-pro:generateContent?key=%s", RandKey(config.Config.App.GeminiKey))
method := "POST"

payload := model.GeminiPro{
Contents: []model.GeminiProContent{
{
Role: "USER",
Parts: []model.GeminiProPart{
{
Text: fmt.Sprintf("你现在是一个视频脚本整合大师。你的任务是将一系列乱序的视频片段整合成一个完整的故事。每个片段都包含了一系列帧的详细内容描述。由于这些片段是乱序的,你需要先找到第 0 片段的第 0 帧,这是视频的开头。从那里开始,确定每个片段及其帧的正确顺序,然后按照这个顺序来分析整个视频的内容。你的最终目标是输出一个连贯的视频内容脚本,该脚本详细地叙述了视频的全部故事线,包括所有关键的对话、场景和情感转变。请注意,你不需要输出处理的过程,只需要提供视频的完整内容概要。现在开始,请查看以下视频片段及其内容描述,并根据这些信息,从第 0 片段的第 0 帧开始,创建一个完整的视频内容脚本:'%s'", content),
},
},
},
},
}
payloadBytes, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("[Error] - error marshaling payload: %v", err), ""
}

client := NewClient()

request, err := http.NewRequest(method, _url, bytes.NewReader(payloadBytes))
if err != nil {
return fmt.Errorf("[Error] - 创建请求失败:%v", err), ""
}

request.Header.Add("Content-Type", "application/json")

response, err := client.Do(request)
if err != nil {
return fmt.Errorf("[Error] - 发送请求失败: %v", err), ""
}

defer response.Body.Close()

body, err := ioutil.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("[Error] - error reading response body: %v", err), ""
}

if response.StatusCode != 200 {
return fmt.Errorf("[Error] - status code error: %d %s", response.StatusCode, string(body)), ""
}

var geminiResponse model.GeminiResponse
if err = json.Unmarshal(body, &geminiResponse); err != nil {
return fmt.Errorf("[Error] - JSON 解析失败 %v", err), ""
}

if len(geminiResponse.Candidates) == 0 {
return fmt.Errorf("[Error] - Candidates 为空"), ""
}

if len(geminiResponse.Candidates[0].Content.Parts) == 0 {
return fmt.Errorf("[Error] - Parts 为空"), ""
}

return nil, geminiResponse.Candidates[0].Content.Parts[0].Text
}
Loading

0 comments on commit 43e01f4

Please sign in to comment.