Initial commit
This commit is contained in:
30
.gitea/workflows/docker_build.yaml
Normal file
30
.gitea/workflows/docker_build.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Build Docker images
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
tags: [ "*" ]
|
||||
|
||||
jobs:
|
||||
acls:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24.3'
|
||||
|
||||
- uses: ko-build/setup-ko@v0.8
|
||||
env:
|
||||
KO_DOCKER_REPO: git.prettysunflower.moe/prettysunflower/youtubethumbnails
|
||||
- env:
|
||||
auth_token: ${{ secrets.HUB_TOKEN }}
|
||||
run: |
|
||||
echo "${auth_token}" | ko login git.prettysunflower.moe --username ${{ vars.HUB_USERNAME }} --password-stdin
|
||||
ko build
|
104
.gitignore
vendored
Normal file
104
.gitignore
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
.idea/sonarlint.xml # see https://community.sonarsource.com/t/is-the-file-idea-idea-idea-sonarlint-xml-intended-to-be-under-source-control/121119
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
10
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
10
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="GoDfaErrorMayBeNotNil" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<functions>
|
||||
<function importPath="net/http" name="Get" />
|
||||
</functions>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/youtubethumbnails.iml" filepath="$PROJECT_DIR$/.idea/youtubethumbnails.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
9
.idea/youtubethumbnails.iml
generated
Normal file
9
.idea/youtubethumbnails.iml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
11
.ko.yaml
Normal file
11
.ko.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
defaultBaseImage: cgr.dev/chainguard/static
|
||||
defaultPlatforms:
|
||||
- linux/arm64
|
||||
- linux/amd64
|
||||
- linux/arm/v7
|
||||
|
||||
builds:
|
||||
- id: youtubethumbnails
|
||||
main: .
|
||||
ldflags:
|
||||
- -s -w
|
109
main.go
Normal file
109
main.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", getRoot)
|
||||
http.HandleFunc("/favicon.ico", getRoot)
|
||||
http.HandleFunc("/{videoId}", getYouTubeThumbnail)
|
||||
|
||||
_ = http.ListenAndServe(":3333", nil)
|
||||
}
|
||||
|
||||
func getYouTubeThumbnail(writer http.ResponseWriter, request *http.Request) {
|
||||
videoId := request.PathValue("videoId")
|
||||
|
||||
resp, err := http.Get("https://img.youtube.com/vi/" + videoId + "/maxresdefault.jpg")
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
_, _ = io.WriteString(writer, string(body))
|
||||
} else if resp.StatusCode == 404 {
|
||||
mqResp, err := http.Get("https://img.youtube.com/vi/" + videoId + "/mqdefault.jpg")
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
imageMq, _, err := image.Decode(mqResp.Body)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(imageMq.Bounds().Max.X, imageMq.Bounds().Max.Y)
|
||||
|
||||
hqResp, err := http.Get("https://img.youtube.com/vi/" + videoId + "/hqdefault.jpg")
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
imageHq, _, err := image.Decode(hqResp.Body)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
imageMqBounds := imageMq.Bounds()
|
||||
imageHqBounds := imageHq.Bounds()
|
||||
|
||||
if imageMqBounds.Max.X > imageMqBounds.Max.Y {
|
||||
fmt.Println("Youtube thumbnail horizontal")
|
||||
fmt.Println("Ideal size")
|
||||
fmt.Print(imageHq.Bounds().Max.X, ", ")
|
||||
idealHeight := imageMqBounds.Max.Y * 100 / (imageMqBounds.Max.X * 100 / imageHqBounds.Max.X)
|
||||
fmt.Println(idealHeight)
|
||||
|
||||
newImage := image.NewRGBA64(image.Rectangle{
|
||||
Min: image.Point{},
|
||||
Max: image.Point{
|
||||
X: imageHqBounds.Max.X, Y: idealHeight,
|
||||
},
|
||||
})
|
||||
|
||||
for i := 0; i < idealHeight; i++ {
|
||||
for j := 0; j < imageHqBounds.Max.X; j++ {
|
||||
iHq := (imageHqBounds.Max.Y / 2) - (idealHeight / 2) + i
|
||||
r, g, b, a := imageHq.At(j, iHq).RGBA()
|
||||
newImage.SetRGBA64(j, i, color.RGBA64{
|
||||
R: uint16(r),
|
||||
G: uint16(g),
|
||||
B: uint16(b),
|
||||
A: uint16(a),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
err := jpeg.Encode(writer, newImage, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
} else {
|
||||
fmt.Println("Youtube thumbnail vertical")
|
||||
}
|
||||
} else {
|
||||
_, _ = io.WriteString(writer, "Something went wrong")
|
||||
}
|
||||
}
|
||||
|
||||
func getRoot(writer http.ResponseWriter, request *http.Request) {
|
||||
_, _ = io.WriteString(writer, "youtubethumbnails is a tool that allows you to get YouTube thumbnails. You can fetch thumbnails with the /{videoId} endpoint.")
|
||||
}
|
Reference in New Issue
Block a user