Last active
December 19, 2015 14:58
-
-
Save landaire/5972565 to your computer and use it in GitHub Desktop.
Code for modifying songs on the fly with Revel, go-taglib, and binarydist
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 controllers | |
import ( | |
"bitbucket.org/kardianos/osext" | |
"bluefish/app/models" | |
"bytes" | |
"crypto/sha1" | |
"errors" | |
"fmt" | |
"github.com/kr/binarydist" | |
"github.com/kr/s3" | |
tag "github.com/landr0id/go-taglib" | |
"github.com/robfig/revel" | |
"github.com/whatupdave/s3/s3util" | |
"io" | |
"io/ioutil" | |
"net/http" | |
u "net/url" | |
"os" | |
"regexp" | |
"strconv" | |
"time" | |
) | |
type Id3 struct { | |
GorpController | |
} | |
const bucketUrl string = "https://s3.amazonaws.com" | |
// Fixes the ID3 tag info for a remote audio file | |
// GET /id3/fix | |
func (c Id3) Fix(url, artist, title string) revel.Result { | |
s3util.DefaultConfig.AccessKey = "" | |
s3util.DefaultConfig.SecretKey = "" | |
// Get the file | |
resp, err := http.Get(url) | |
if match, _ := regexp.MatchString(`unsupported protocol scheme ""`, fmt.Sprintf("%v", err)); match { | |
// They want a relative URL -- this could be used for internal testing for calling /x.mp3 | |
resp, err = http.Get("http://127.0.0.1" + url) | |
} | |
// Check the err | |
if err != nil { | |
return c.RenderError(err) | |
} | |
if resp == nil { | |
return c.RenderError(errors.New("Invalid response")) | |
} | |
if resp.StatusCode != 200 { | |
return c.RenderError(errors.New("Response status code was not 200 - OK. Got: " + resp.Status)) | |
} | |
defer resp.Body.Close() | |
// Make sure we were given an audio file by checking the content type | |
if match, _ := regexp.MatchString(`audio\.+`, resp.Header.Get("Content-Type")); match { | |
return c.RenderError(errors.New("Not an audio file")) | |
} | |
body, err := ioutil.ReadAll(resp.Body) | |
if err != nil { | |
return c.RenderError(err) | |
} | |
location, err := u.Parse(url) | |
if err != nil { | |
return c.RenderError(err) | |
} | |
// Create a hash of the URL (without the parameters) | |
sha := sha1.New() | |
fmt.Println(location.Host + location.Path) | |
sha.Write([]byte(location.Host + location.Path)) | |
diffName := fmt.Sprintf("%X.diff", sha.Sum(nil)) | |
c.Response.Status = http.StatusOK | |
c.Response.ContentType = "audio/mpeg" | |
if checkIfDiffExists(diffName, c) { | |
fmt.Println("Diff exists -- using that") | |
// Write the body to a buffer, then get rid of the body | |
oldbuf := bytes.NewBuffer(body) | |
var newbuf bytes.Buffer | |
var finalbuf bytes.Buffer | |
fmt.Println(newbuf.Len()) | |
diffReader, err := getDiff(diffName) | |
if err != nil { | |
return c.RenderError(err) | |
} | |
defer diffReader.Close() | |
err = binarydist.Patch(oldbuf, &newbuf, diffReader) | |
finalbuf.Write(newbuf.Bytes()) | |
//finalbuf.Write(body[newbuf.Len():len(body)]) | |
if err != nil { | |
return c.RenderError(err) | |
} | |
return &revel.BinaryResult{ | |
Reader: &newbuf, | |
Name: fmt.Sprintf("%s - %s.mp3", artist, title), | |
Delivery: "attachment", | |
Length: int64(newbuf.Len()), | |
ModTime: time.Now(), | |
} | |
} else { | |
fmt.Println("Diff does not exist. Creating new one.") | |
result, err := createNewDiff(c, diffName, artist, title, body) | |
if err == nil { | |
fmt.Println("adding to database") | |
addDiffInfoToDatabase(diffName, artist, title, url, c) | |
} | |
return result | |
} | |
return c.RenderError(errors.New(strconv.FormatBool(checkIfDiffExists(diffName, c)))) | |
} | |
// checkIfDiffExists checks the SQLite3 DB (songs.db) to see if the diff has already been created, and exists | |
// on the server | |
func checkIfDiffExists(name string, c Id3) bool { | |
// Run query to see if a row in the DB with the name exists | |
result, err := c.Txn.Select(&models.DiffPatch{}, `SELECT * FROM DiffPatch where Name = ?`, name) | |
// There will be an error returned if the query is fucked or something idk | |
if err != nil { | |
fmt.Println(err) | |
return false | |
} | |
// gorp.Transaction.Select returns an array for the result, so if the array is of length 0 then | |
// that means that we didn't get jack shit | |
if result != nil && len(result) != 0 { | |
// Cast the first index from the result array as a DiffPatch object | |
x := result[0].(*models.DiffPatch) | |
// Increase the request count on the object | |
x.RequestCount++ | |
// Update the model (really just updating the request count) | |
c.Txn.Update(x) | |
return true | |
} | |
return false | |
} | |
// getDiff returns an io.ReadCloser after opening the file from the S3 bucket | |
func getDiff(name string) (io.ReadCloser, error) { | |
// Opens the object on the bucket server | |
r, err := s3util.Open(fmt.Sprintf("%s/%s", bucketUrl, name), nil) | |
if err != nil { | |
fmt.Println(err) | |
return nil, err | |
} | |
return r, nil | |
} | |
// uploadDiff uploads the diff patch to the S3 bucket | |
func uploadDiff(name string, data []byte) error { | |
// Creates the object on the bucket server | |
w, err := s3util.Create(fmt.Sprintf("%s/%s", bucketUrl, name), nil, nil) | |
if err != nil { | |
fmt.Println(err) | |
return err | |
} | |
defer w.Close() | |
// Write out the patch data | |
w.Write(data) | |
return nil | |
} | |
// addDiffInfoToDatabse takes the relevant info, stores it in the SQLite3 DB | |
// returns an error if gorp.Transaction.Insert returns an error | |
func addDiffInfoToDatabase(name, artist, title, url string, c Id3) error { | |
// This will only be the DATE, no time. | |
dateAdded := time.Now() | |
diff := &models.DiffPatch{0, name, url, artist, title, dateAdded.Format("2006-01-02"), dateAdded, 1} | |
// Insert the row into the DB | |
if err := c.Txn.Insert(diff); err != nil { | |
return err | |
} | |
return nil | |
} | |
/* makeRequest is not really needed now, however it will remain until I'm for sure that I don't need to make | |
* manual requests | |
*/ | |
func makeRequest(path, method string, content io.Reader, contentLength int64) *http.Request { | |
fmt.Println(path) | |
r, _ := http.NewRequest(method, path, content) | |
r.ContentLength = contentLength | |
r.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat)) | |
r.Header.Set("X-Amz-Acl", "public-read") | |
s3.Sign(r, *s3util.DefaultConfig.Keys) | |
return r | |
} | |
func createNewDiff(c Id3, diffName, artist, title string, data []byte) (revel.Result, error) { | |
filename, _ := osext.ExecutableFolder() | |
// Great, let's write this data out to a file | |
tempName := filename + "tmp_" + generateRandomString() + ".mp3" | |
file, err := os.Create(tempName) | |
if err != nil { | |
fmt.Println("Error creating file:", err) | |
return c.RenderError(err), err | |
} | |
defer os.Remove(tempName) | |
file.Write(data) | |
file.Close() | |
// Now that the data is written to a file, do some taglib stuff | |
tFile, err := tag.Read(tempName) | |
if err != nil { | |
fmt.Printf("Error reading file %s: %v\n", tempName, err) | |
return c.RenderError(err), err | |
} | |
if tFile == nil { | |
fmt.Println("Nil file") | |
return c.RenderError(err), err | |
} | |
songTitle := tFile.Title() | |
songArtist := tFile.Artist() | |
// Set the data if they don't exist | |
if songTitle == "" || songArtist == "" { | |
tFile.SetTitle(title) | |
tFile.SetArtist(artist) | |
tFile.Save() | |
} | |
file.Close() | |
// Now do the diff stuff | |
file, _ = os.Open(tempName) | |
// The info is required for the mod time | |
info, err := file.Stat() | |
if err != nil { | |
file.Close() | |
return c.RenderError(err), err | |
} | |
// oldbuf holds the original data | |
var oldbuf bytes.Buffer | |
// patch holds the patch data | |
var patch bytes.Buffer | |
// Make sure that we free the memory | |
defer oldbuf.Reset() | |
defer patch.Reset() | |
// Write out the body data (what was given in the http.Response) to the original | |
oldbuf.Write(data) | |
// Clear the original data -- we don't need it | |
data = nil | |
// Generate the patch | |
binarydist.Diff(&oldbuf, file, &patch) | |
// Upload the diff to the S3 bucket | |
err = uploadDiff(diffName, patch.Bytes()) | |
if err != nil { | |
file.Close() | |
return c.RenderError(err), err | |
} | |
// Seek for good measure | |
file.Seek(0, os.SEEK_SET) | |
return &revel.BinaryResult{ | |
Reader: file, | |
Name: fmt.Sprintf("%s - %s.mp3", artist, title), | |
Delivery: "attachment", | |
Length: -1, | |
ModTime: info.ModTime(), | |
}, nil | |
} | |
func generateRandomString() string { | |
utime := time.Now().Unix() | |
buf := []byte{ | |
byte(utime >> 56), | |
byte((utime & 0x00FF000000000000) >> 48), | |
byte((utime & 0x0000FF0000000000) >> 40), | |
byte((utime & 0x000000FF00000000) >> 32), | |
byte((utime & 0x00000000FF000000) >> 24), | |
byte((utime & 0x0000000000FF0000) >> 16), | |
byte((utime & 0x000000000000FF00) >> 8), | |
byte(utime & 0x00000000000000FF), | |
} | |
hash := sha1.New() | |
hash.Write(buf) | |
return fmt.Sprintf("%X", hash.Sum(nil)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment