imgur-rescue-project/irp2bolt/main.go

273 lines
5.6 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"time"
"go.etcd.io/bbolt"
)
type ImgurSingleMedia struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
MimeType string `json:"mime_type"`
CreatedAt time.Time `json:"created_at"` // e.g. 2013-10-28T02:37:02Z
Width int64 `json:"width"`
Height int64 `json:"height"`
Extension string `json:"ext"`
}
func (ism ImgurSingleMedia) InventName() string {
ret := ism.Title
if len(ism.Description) > 0 {
if len(ret) > 0 {
ret += " - "
}
ret += ism.Description
}
if len(ret) == 0 {
// No name/description in either gallery nor in first image
// Guess we just name it after the ID
ret = ism.ID
}
ret += "." + ism.Extension
return ret
}
type ImgurInfo struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
Media []ImgurSingleMedia `json:"media"`
}
func (i ImgurInfo) AlbumJson() []byte {
arr := make([]string, 0, len(i.Media))
for _, m := range i.Media {
arr = append(arr, m.ID)
}
bb, err := json.Marshal(arr)
if err != nil {
panic(err)
}
return bb
}
func (i ImgurInfo) InventName() string {
ret := i.Title
if len(i.Description) > 0 {
if len(ret) > 0 {
ret += " - "
}
ret += i.Description
}
if len(ret) > 0 {
return ret // Title + description is pretty good for an album
}
if len(i.Media) > 0 {
// Describe this album based on the first media instead
return i.Media[0].InventName()
}
// No name/description in either gallery nor in first image
// Guess we just name it after the ID
return i.ID
}
type ContentedMetadata struct {
FileHash string
FileSize int64
UploadTime time.Time
UploadIP string
Filename string
MimeType string
}
func main() {
db, err := bbolt.Open(fmt.Sprintf("output-%d.db", time.Now().Unix()), 0644, bbolt.DefaultOptions)
if err != nil {
panic(err)
}
err = db.Update(func(tx *bbolt.Tx) error {
bb, err := tx.CreateBucketIfNotExists([]byte(`METADATA`))
if err != nil {
panic(err)
}
//
// Media
//
media, err := os.ReadDir("../metadata.media")
if err != nil {
panic(err)
}
var addMediaCount int64 = 0
for _, mediaInfo := range media {
infoJson, err := ioutil.ReadFile("../metadata.media/" + mediaInfo.Name())
if err != nil {
panic(err)
}
var info ImgurInfo
err = json.Unmarshal(infoJson, &info)
if err != nil {
panic(err)
}
if len(info.Media) != 1 {
panic(err)
}
// Ensure image file exists
finfo, err := os.Stat("../images/" + mediaInfo.Name())
if err != nil {
log.Printf("Missing image %s for media %s, skipping", mediaInfo.Name(), mediaInfo.Name())
continue
// panic(err)
}
cinfoBytes, err := json.Marshal(ContentedMetadata{
FileHash: mediaInfo.Name(),
FileSize: finfo.Size(),
UploadTime: info.CreatedAt,
UploadIP: "n/a",
Filename: info.InventName(),
MimeType: info.Media[0].MimeType,
})
if err != nil {
panic(err)
}
err = bb.Put([]byte(mediaInfo.Name()), cinfoBytes)
if err != nil {
panic(err)
}
addMediaCount += 1
}
log.Printf("Added %d media entries OK", addMediaCount)
//
// Albums
//
albums, err := os.ReadDir("../metadata.albums")
if err != nil {
panic(err)
}
var addAlbumCount int64 = 0
var addAlbumMediaCount int64 = 0
var albumsWithNoImagesCount int64 = 0
for _, albuminfo := range albums {
infoJson, err := ioutil.ReadFile("../metadata.albums/" + albuminfo.Name())
if err != nil {
panic(err)
}
var info ImgurInfo
err = json.Unmarshal(infoJson, &info)
if err != nil {
panic(err)
}
if len(info.Media) == 0 {
log.Printf("Album '%s' contains no images, allowing anyway", albuminfo.Name())
albumsWithNoImagesCount += 1
}
// Add gallery entries for each of the media elements
for _, mediaInfo := range info.Media {
// Ensure image file exists
finfo, err := os.Stat("../images/" + mediaInfo.ID)
if err != nil {
log.Printf("Missing image %s for album %s, skipping", mediaInfo.ID, albuminfo.Name())
continue
// panic(err)
}
cinfoBytes, err := json.Marshal(ContentedMetadata{
FileHash: mediaInfo.ID,
FileSize: finfo.Size(),
UploadTime: info.CreatedAt,
UploadIP: "n/a",
Filename: mediaInfo.InventName(),
MimeType: mediaInfo.MimeType,
})
if err != nil {
panic(err)
}
err = bb.Put([]byte(mediaInfo.ID), cinfoBytes)
if err != nil {
panic(err)
}
addAlbumMediaCount += 1
}
// Add album entry for the overall album
albumHash := `a/` + albuminfo.Name() // Use a/ prefix. This can't naturally happen in contented's filehash algorithm
albumJson := info.AlbumJson()
err = ioutil.WriteFile(albumHash, albumJson, 0644) // a/ subdirectory must exist
if err != nil {
panic(err)
}
cinfoBytes, err := json.Marshal(ContentedMetadata{
FileHash: albumHash,
FileSize: int64(len(albumJson)),
UploadTime: info.CreatedAt,
UploadIP: "n/a",
Filename: info.InventName(),
MimeType: "contented/album",
})
if err != nil {
panic(err)
}
err = bb.Put([]byte(albumHash), cinfoBytes)
if err != nil {
panic(err)
}
addAlbumCount += 1
}
log.Printf("Added %d album entries OK with %d additional image entries", addAlbumCount, addMediaCount)
log.Printf("There are %d albums with no images", albumsWithNoImagesCount)
// Fully imported
return nil
})
if err != nil {
panic(err)
}
}