Avatar improvements

This commit is contained in:
Eduard Urbach 2017-06-13 00:06:35 +02:00
parent d3f49ad53d
commit 5d97a87152
11 changed files with 171 additions and 145 deletions

View File

@ -1,18 +1,21 @@
package main package main
import ( import (
"io/ioutil"
"os" "os"
"runtime" "runtime"
"time" "time"
"github.com/animenotifier/arn" "github.com/animenotifier/arn"
"github.com/fatih/color" )
"github.com/parnurzeal/gorequest"
gravatar "github.com/ungerik/go-gravatar" const (
avatarsDirectoryOriginal = "images/avatars/original/"
avatarsDirectoryWebP = "images/avatars/webp/"
) )
func main() { func main() {
os.Chdir("../../")
users, _ := arn.AllUsers() users, _ := arn.AllUsers()
usersQueue := make(chan *arn.User) usersQueue := make(chan *arn.User)
@ -23,6 +26,7 @@ func main() {
go func(workerID int) { go func(workerID int) {
for user := range usersQueue { for user := range usersQueue {
<-rateLimiter.C <-rateLimiter.C
if downloadAvatar(user) { if downloadAvatar(user) {
makeWebPAvatar(user) makeWebPAvatar(user)
user.Avatar = "/+" + user.Nick + "/avatar" user.Avatar = "/+" + user.Nick + "/avatar"
@ -39,90 +43,3 @@ func main() {
usersQueue <- user usersQueue <- user
} }
} }
func findAvatar(user *arn.User, dir string) string {
testExtensions := []string{".jpg", ".png", ".gif", ".webp", ""}
for _, testExt := range testExtensions {
if _, err := os.Stat(dir + user.ID + testExt); !os.IsNotExist(err) {
return user.ID + testExt
}
}
return ""
}
func makeWebPAvatar(user *arn.User) {
baseName := findAvatar(user, "../../images/avatars/original/")
if baseName == "" {
return
}
original := "../../images/avatars/original/" + baseName
outFile := "../../images/avatars/webp/" + user.ID + ".webp"
err := convertFileToWebP(original, outFile, 80)
if err != nil {
color.Red("[WebP] " + original + " -> " + outFile)
} else {
color.Green("[WebP] " + original + " -> " + outFile)
}
}
func downloadAvatar(user *arn.User) bool {
if user.Email == "" {
return false
}
directory := "../../images/avatars/original/"
fileName := directory + user.ID
// Build URL
url := gravatar.Url(user.Email) + "?s=560&d=404&r=pg"
// Skip existing avatars
if findAvatar(user, directory) != "" {
color.Yellow(url)
return true
}
// Download
response, data, err := gorequest.New().Get(url).EndBytes()
if err != nil {
color.Red(url)
return false
}
contentType := response.Header.Get("content-type")
if response.StatusCode != 200 {
color.Red(url)
return false
}
color.Green(url)
// Determine file extension
extension := ""
switch contentType {
case "image/jpeg":
extension = ".jpg"
case "image/png":
extension = ".png"
case "image/gif":
extension = ".gif"
case "image/webp":
extension = ".webp"
}
fileName += extension
// Write to disk
ioutil.WriteFile(fileName, data, 0644)
return true
}

79
jobs/avatars/download.go Normal file
View File

@ -0,0 +1,79 @@
package main
import (
"fmt"
"io/ioutil"
"github.com/animenotifier/arn"
"github.com/fatih/color"
"github.com/parnurzeal/gorequest"
gravatar "github.com/ungerik/go-gravatar"
)
func findOriginalAvatar(user *arn.User) string {
return arn.FindFileWithExtension(
user.ID,
avatarsDirectoryOriginal,
[]string{
".jpg",
".png",
".gif",
},
)
}
func downloadAvatar(user *arn.User) bool {
if user.Email == "" {
return false
}
directory := avatarsDirectoryOriginal
fileName := directory + user.ID
// Build URL
url := gravatar.Url(user.Email) + "?s=" + fmt.Sprint(arn.AvatarMaxSize) + "&d=404&r=pg"
// Skip existing avatars
if findOriginalAvatar(user) != "" {
color.Yellow(user.Nick)
return true
}
// Download
response, data, err := gorequest.New().Get(url).EndBytes()
if err != nil {
color.Red(user.Nick)
return false
}
contentType := response.Header.Get("content-type")
if response.StatusCode != 200 {
color.Red(user.Nick)
return false
}
// Determine file extension
extension := ""
switch contentType {
case "image/jpeg":
extension = ".jpg"
case "image/png":
extension = ".png"
case "image/gif":
extension = ".gif"
case "image/webp":
extension = ".webp"
}
fileName += extension
// Write to disk
ioutil.WriteFile(fileName, data, 0644)
color.Green(user.Nick)
return true
}

View File

@ -1,40 +1,51 @@
package main package main
import ( import (
"fmt"
"image"
_ "image/gif" _ "image/gif"
_ "image/jpeg" _ "image/jpeg"
_ "image/png" _ "image/png"
"os" "strings"
"github.com/chai2010/webp" "github.com/animenotifier/arn"
"github.com/fatih/color"
"github.com/nfnt/resize"
) )
func convertFileToWebP(in string, out string, quality float32) error { func makeWebPAvatar(user *arn.User) {
f, openErr := os.Open(in) baseName := findOriginalAvatar(user)
if openErr != nil { if baseName == "" {
return openErr return
} }
img, format, decodeErr := image.Decode(f) original := avatarsDirectoryOriginal + baseName
outFile := avatarsDirectoryWebP + user.ID + ".webp"
if decodeErr != nil { err := avatarToWebP(original, outFile, 80)
return decodeErr
if err != nil {
color.Red(user.Nick + " [WebP]")
} else {
color.Green(user.Nick + " [WebP]")
} }
}
fmt.Println(format, img.Bounds().Dx(), img.Bounds().Dy())
func avatarToWebP(in string, out string, quality float32) error {
fileOut, writeErr := os.Create(out) img, _, loadErr := arn.LoadImage(in)
if writeErr != nil { if loadErr != nil {
return writeErr return loadErr
} }
encodeErr := webp.Encode(fileOut, img, &webp.Options{ // Big avatar
Quality: quality, saveErr := arn.SaveWebP(img, out, quality)
})
if saveErr != nil {
return encodeErr return saveErr
}
// Small avatar
smallImg := resize.Resize(arn.AvatarSmallSize, 0, img, resize.Lanczos3)
saveErr = arn.SaveWebP(smallImg, strings.Replace(out, ".webp", ".small.webp", 1), quality)
return saveErr
} }

17
main.go
View File

@ -96,6 +96,23 @@ func main() {
return ctx.Error(http.StatusBadRequest, err.Error(), err) return ctx.Error(http.StatusBadRequest, err.Error(), err)
}) })
// Avatars
app.Get("/user/:nick/avatar/small", func(ctx *aero.Context) string {
nick := ctx.Get("nick")
user, err := arn.GetUserByNick(nick)
if err != nil {
return ctx.Error(http.StatusNotFound, "User not found", err)
}
if ctx.CanUseWebP() {
return ctx.File("images/avatars/webp/" + user.ID + ".small.webp")
}
err = errors.New("Your browser doesn't support the WebP image format")
return ctx.Error(http.StatusBadRequest, err.Error(), err)
})
// Elements // Elements
app.Get("/images/elements/:file", func(ctx *aero.Context) string { app.Get("/images/elements/:file", func(ctx *aero.Context) string {
return ctx.File("images/elements/" + ctx.Get("file")) return ctx.File("images/elements/" + ctx.Get("file"))

View File

@ -4,15 +4,12 @@ component Avatar(user *arn.User)
component AvatarNoLink(user *arn.User) component AvatarNoLink(user *arn.User)
if user.Avatar != "" if user.Avatar != ""
if strings.Contains(user.Avatar, "gravatar.com") img.user-image(src=user.Avatar + "/small", alt=user.Nick)
img.user-image(src=user.Avatar + "?s=100&r=x&d=mm", alt=user.Nick)
else
img.user-image(src=user.Avatar, alt=user.Nick)
else else
SVGAvatar(50) SVGAvatar
component SVGAvatar(size int) component SVGAvatar
svg.user-image(width=size, height=size, viewBox="0 0 50 50") svg.user-image(viewBox="0 0 50 50")
circle.head(cx="25", cy="19", r="10") circle.head(cx="25", cy="19", r="10")
circle.body(cx="25", cy="50", r="20") circle.body(cx="25", cy="50", r="20")
//- text(x="25", y="44", text-anchor="middle") TODO //- text(x="25", y="44", text-anchor="middle") TODO

View File

@ -1,7 +1,7 @@
component ProfileImage(user *arn.User) component ProfileImage(user *arn.User)
if user.Avatar != "" if user.Avatar != ""
img.profile-image(src=user.Avatar + "?s=560&r=x&d=mm", alt="Profile image") img.profile-image(src=user.Avatar, alt="Profile image")
else else
svg.profile-image(width=280, height=280, viewBox="0 0 50 50", alt="Profile image") svg.profile-image(viewBox="0 0 50 50", alt="Profile image")
circle.head(cx="25", cy="19", r="10") circle.head(cx="25", cy="19", r="10")
circle.body(cx="25", cy="50", r="20") circle.body(cx="25", cy="50", r="20")

13
mixins/ThreadLink.pixy Normal file
View File

@ -0,0 +1,13 @@
component ThreadLink(thread *arn.Thread)
.thread-link(data-sticky=thread.Sticky)
.post-author.thread-author
Avatar(thread.Author())
.thread-content-container
.thread-content
if thread.Sticky
Icon("thumb-tack")
a.thread-link-title.ajax(href="/threads/" + thread.ID)= thread.Title
.spacer
.thread-reply-count= thread.Replies
.thread-icons
Icon(arn.GetForumIcon(thread.Tags[0]))

View File

@ -8,17 +8,3 @@ component Forum(tag string, threads []*arn.Thread)
component ThreadList(threads []*arn.Thread) component ThreadList(threads []*arn.Thread)
each thread in threads each thread in threads
ThreadLink(thread) ThreadLink(thread)
component ThreadLink(thread *arn.Thread)
.thread-link(data-sticky=thread.Sticky)
.post-author.thread-author
Avatar(thread.Author())
.thread-content-container
.thread-content
if thread.Sticky
Icon("thumb-tack")
a.thread-link-title.ajax(href="/threads/" + thread.ID)= thread.Title
.spacer
.thread-reply-count= thread.Replies
.thread-icons
Icon(arn.GetForumIcon(thread.Tags[0]))

View File

@ -17,10 +17,10 @@ component Profile(viewUser *arn.User, user *arn.User, animeList *arn.AnimeList,
Icon("comment") Icon("comment")
span.tagline-text No tagline yet. span.tagline-text No tagline yet.
//- if user != nil && viewUser.website if viewUser.Website != ""
//- p.profile-field.website p.profile-field.website
//- Icon("home") Icon("home")
//- a(href=viewUser.website.startsWith('http') ? viewUser.website : 'http://' + viewUser.website, target='_blank', rel='nofollow')= viewUser.website.replace('http://', '').replace('https://', '') a(href=viewUser.WebsiteURL(), target="_blank", rel="nofollow")= viewUser.Website
if viewUser.Accounts.Osu.Nick != "" && viewUser.Accounts.Osu.PP >= 1000 if viewUser.Accounts.Osu.Nick != "" && viewUser.Accounts.Osu.PP >= 1000
p.profile-field.osu(title="osu! performance points") p.profile-field.osu(title="osu! performance points")

View File

@ -1,2 +1,7 @@
.user-avatars .user-avatars
horizontal-wrap horizontal-wrap
justify-content center
border-radius 3px
.user-image
margin 0.4rem

View File

@ -1,4 +1,5 @@
.user-image .user-image
display block
width avatar-size width avatar-size
height avatar-size height avatar-size
border-radius 100% border-radius 100%