Skip to content

Instantly share code, notes, and snippets.

@Necroforger
Created February 25, 2019 23:31
Show Gist options
  • Save Necroforger/1037b763d2940d2ced3c2e2899d86397 to your computer and use it in GitHub Desktop.
Save Necroforger/1037b763d2940d2ced3c2e2899d86397 to your computer and use it in GitHub Desktop.
script to help with re-creating old videos given a thumbnail and audio file. requires ffmpeg.
package main
import (
"database/sql"
"flag"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"strings"
"github.com/nfnt/resize"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
_ "github.com/mattn/go-sqlite3"
)
var (
archive = flag.String("archive", ``, "path of archive file")
videoID = flag.String("id", "", "id of the video to search for")
audioSource = flag.String("audio", "", "source of audio file to download")
convertAudio = flag.String("convert-audio", "", "convert audio to the given format")
thumbnail = flag.String("thumb", "", "thumbnail file")
width = flag.Int("width", 1920, "width of output video")
height = flag.Int("height", 1080, "height of output video")
skipVideo = flag.Bool("skip-video", false, "skip creating the video")
)
// Video stores important video information
type Video struct {
ID string
Uploader string
UploadDate string
Description string
Likes int
Views int
Title string
}
func isHTTP(URL string) bool {
return strings.HasPrefix(URL, "http://") || strings.HasPrefix(URL, "https://")
}
func getFile(URL string) (io.ReadCloser, error) {
if isHTTP(URL) {
resp, err := http.Get(URL)
if err != nil {
return nil, err
}
return resp.Body, nil
}
return os.Open(URL)
}
func downloadWithYoutubedl(URL string, dest string) error {
ytdl := exec.Command("youtube-dl", URL, "-o", "-")
out, err := ytdl.StdoutPipe()
if err != nil {
return err
}
err = ytdl.Start()
if err != nil {
return err
}
f, err := os.OpenFile(dest+".ytdl", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
if err != nil {
return err
}
defer f.Close()
io.Copy(f, out)
ytdl.Wait()
convertFile(dest+".ytdl", dest)
return err
}
func convertFile(src, dst string) error {
return exec.Command("ffmpeg", "-i", src, dst).Run()
}
// downloadFile downloads your file to the given location or returns the resource path
// if a download isn't necessary
func downloadFile(URL string, dest string) (path string, err error) {
if isHTTP(URL) {
// Use youtube-dl
if strings.Contains(URL, "youtu.be") {
err := downloadWithYoutubedl(URL, dest+".mp3")
return dest + ".mp3", err
}
// Use http
resp, err := http.Get(URL)
if err != nil {
return "", err
}
f := resp.Body
defer f.Close()
o, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
return "", err
}
defer o.Close()
_, err = io.Copy(o, f)
if err != nil {
return "", err
}
return dest, nil
}
return URL, nil
}
func resizeImage(imgpath string, dest string, width, height uint) error {
log.Println("getting thumbnail image")
f, err := getFile(imgpath)
if err != nil {
return err
}
log.Println("decoding image")
img, _, err := image.Decode(f)
if err != nil {
f.Close()
return err
}
f.Close()
log.Println("creating thumbnail")
img = resize.Resize(width, 0, img, resize.Lanczos3)
img = resize.Thumbnail(width, height, img, resize.Lanczos3)
a := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
log.Println("original bounds: ", img.Bounds())
log.Println("new bounds: ", a.Bounds())
widthDifference := a.Bounds().Dx() - img.Bounds().Dx()
var padding = 0
if widthDifference > 0 {
padding = widthDifference / 2
} else {
log.Println("there is no width difference between the two images")
}
log.Println("image padding: ", padding)
// Fill the image with black pixels
draw.Draw(a, a.Bounds(), image.NewUniform(color.Black), image.ZP, draw.Src)
// Place the image centered on top with the padding
draw.Draw(a, image.Rect(padding, 0, a.Bounds().Dx(), a.Bounds().Dy()), img, image.ZP, draw.Over)
log.Println("saving new thumbnail to disk")
out, err := os.OpenFile(dest, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
defer out.Close()
return png.Encode(out, a)
}
// attempt to get the thumbnail URL from the video description if it isn't already set
func getThumbnail(v Video) string {
if *thumbnail != "" {
return *thumbnail
}
return ""
}
func getVideo(db *sql.DB, id string) (Video, error) {
var v = Video{}
err := db.QueryRow("SELECT video_id, uploader, upload_date, description, likes, views, title FROM videos WHERE video_id = ?", id).
Scan(&v.ID, &v.Uploader, &v.UploadDate, &v.Description, &v.Likes, &v.Views, &v.Title)
return v, err
}
func formatDescription(v Video) string {
formattedDescription :=
"======== Original Stats ========\n" +
"Uploader : %s\n" +
"Video ID : %s\n" +
"Likes : %d\n" +
"Views : %d\n" +
"Upload Date : %s\n" +
"================================\n\n" +
"%s"
return fmt.Sprintf(formattedDescription, v.Uploader, v.ID, v.Likes, v.Views, formatDate(v.UploadDate), v.Description)
}
func formatDate(d string) string {
return d[0:4] + "-" + d[4:6] + "-" + d[6:8]
}
func main() {
flag.Parse()
var video Video
var err error
// Get video information for the original video if an archive file specified
if *archive != "" {
log.Println("opening connection to database")
// Open a database connection
db, err := sql.Open("sqlite3", *archive)
if err != nil {
log.Fatal("error opening database connection: ", err)
}
log.Println("searching for video")
// Get the video information from database
video, err = getVideo(db, *videoID)
if err != nil {
log.Fatal("error retrieving given video from database: ", err)
}
log.Println("formatting description")
// Format a description for this video
description := formatDescription(video)
err = ioutil.WriteFile(video.ID+".txt", []byte(description), 0600)
if err != nil {
log.Fatal("error creating description file: ", err)
}
// Write a file containing the title of the video
err = ioutil.WriteFile(video.ID+"_title.txt", []byte(video.Title), 0600)
if err != nil {
log.Fatal("error creating video title file")
}
} else {
video.ID = "output"
}
if *skipVideo {
return
}
log.Println("retrieving image")
// Download or get the thumbnail from the local hard drive
thumbnailPath, err := downloadFile(getThumbnail(video), "create-video.thumbnail")
if err != nil {
log.Fatal("could not download thumbnail: ", err)
}
log.Println("retrieving audio")
// Download the audio from a direct link or use youtube-dl to download from youtube
// Then convert the resulting audio to mp3
audioPath, err := downloadFile(*audioSource, "create-video.audio")
if err != nil {
log.Fatal("could not download audio: ", err)
}
log.Println("resizing image")
// Resize the image to the given bounds with padding
err = resizeImage(thumbnailPath, "create-video.thumbnail.png", uint(*width), uint(*height))
if err != nil {
log.Fatal("error resizing thumbnail", err)
}
if *convertAudio != "" {
log.Println("converting audio format")
err := convertFile(audioPath, audioPath+".mp3")
if err != nil {
log.Fatal("error converting audio: ", err)
}
audioPath = audioPath + ".mp3"
}
log.Println("creating video")
// Build the video
ffmpeg := exec.Command("ffmpeg", "-loop", "1", "-i", "create-video.thumbnail.png", "-i", audioPath, "-shortest", "-c:a", "copy", video.ID+".mp4")
ffmpeg.Stdout = os.Stdout
ffmpeg.Stderr = os.Stderr
err = ffmpeg.Run()
if err != nil {
log.Fatal("error running ffmpeg: ", err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment