Created
February 25, 2019 23:31
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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