Skip to content

Commit dc95c57

Browse files
committed
initial commit
0 parents  commit dc95c57

File tree

357 files changed

+151635
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

357 files changed

+151635
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/albums

LICENCE

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2017 Rene Kaufmann
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
this software and associated documentation files (the "Software"), to deal in
5+
the Software without restriction, including without limitation the rights to
6+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7+
of the Software, and to permit persons to whom the Software is furnished to do
8+
so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.

confirm.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"log"
7+
"os"
8+
"strings"
9+
)
10+
11+
// askForConfirmation asks the user for confirmation. A user must type in "yes" or "no" and
12+
// then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as
13+
// confirmations. If the input is not recognized, it will ask again. The function does not return
14+
// until it gets a valid response from the user.
15+
func askForConfirmation(s string) bool {
16+
reader := bufio.NewReader(os.Stdin)
17+
18+
for {
19+
fmt.Printf("%s [y/n]: ", s)
20+
21+
response, err := reader.ReadString('\n')
22+
if err != nil {
23+
log.Fatal(err)
24+
}
25+
26+
response = strings.ToLower(strings.TrimSpace(response))
27+
28+
if response == "y" || response == "yes" {
29+
return true
30+
} else if response == "n" || response == "no" {
31+
return false
32+
}
33+
}
34+
}

ffmpeg.go

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
"path/filepath"
7+
"regexp"
8+
"strings"
9+
10+
"github.com/bogem/id3v2"
11+
"github.com/cheggaaa/pb"
12+
"github.com/michiwend/gomusicbrainz"
13+
"github.com/pkg/errors"
14+
)
15+
16+
var errInvalidInput = errors.New("invalid duration")
17+
18+
func getLength(inputFile string) (int, error) {
19+
re := regexp.MustCompile("Duration: [0-9]+:[0-9]+:[0-9]+")
20+
21+
cmd := exec.Command("ffmpeg", "-i", inputFile)
22+
stdoutStderr, err := cmd.CombinedOutput()
23+
if err != nil && err.Error() != "exit status 1" {
24+
return 0, err
25+
}
26+
27+
return durToSec(strings.TrimPrefix(re.FindString(string(stdoutStderr)), "Duration: "))
28+
}
29+
30+
func transcode(inputFile, outputFile string, start, length float64) error {
31+
cmd := exec.Command("ffmpeg", "-y", "-i", inputFile, "-ss", fmt.Sprintf("%.0f", start),
32+
"-t", fmt.Sprintf("%.0f", length),
33+
"-codec:a", "libmp3lame", "-qscale:a", "3",
34+
outputFile)
35+
return cmd.Run()
36+
}
37+
38+
func convertTrack(inputFile string, mbr musicBrainzRecording, dlFolder string) error {
39+
l, err := getLength(inputFile)
40+
if err != nil {
41+
return errors.Wrap(err, "getLength failed")
42+
}
43+
44+
trackName := fmt.Sprintf("%.2d %s - %s", mbr.trackNum, mbr.artist, mbr.trackTitle)
45+
trackFullPath := filepath.Join(dlFolder, trackName) + "." + "mp3"
46+
47+
if err := transcode(inputFile, trackFullPath, 0.0, float64(l)); err != nil {
48+
return errors.Wrap(err, "transcode failed")
49+
}
50+
51+
err = tagFile(trackFullPath, mbr.artist, mbr.trackTitle, mbr.year, mbr.albumTitle, int(mbr.trackNum), int(mbr.trackCount), mbr.cdNum)
52+
if err != nil {
53+
return errors.Wrap(err, "tagFile failed")
54+
}
55+
return nil
56+
}
57+
58+
func extractTracks(inputFile, year, album string, tracks []float64, trackInfo []*gomusicbrainz.Track, dlFolder string) error {
59+
l, err := getLength(inputFile)
60+
if err != nil {
61+
return errors.Wrap(err, "getLength failed")
62+
}
63+
64+
tracks = append(tracks, float64(l))
65+
66+
bar := pb.StartNew(len(trackInfo))
67+
defer bar.Finish()
68+
69+
for i := 0; i < len(tracks)-1; i++ {
70+
if len(trackInfo) > i {
71+
72+
artist := trackInfo[i].Recording.ArtistCredit.NameCredits[0].Artist.Name
73+
title := trackInfo[i].Recording.Title
74+
75+
trackName := fmt.Sprintf("%.2d %s - %s", i+1, artist, title)
76+
trackFullPath := filepath.Join(dlFolder, trackName) + "." + "mp3"
77+
start := tracks[i]
78+
end := tracks[i+1]
79+
length := end - start
80+
81+
if err := transcode(inputFile, trackFullPath, start, length); err != nil {
82+
return errors.Wrap(err, "transcode failed")
83+
}
84+
85+
err = tagFile(trackFullPath, artist, title, year, album, i+1, len(trackInfo), -1)
86+
if err != nil {
87+
return errors.Wrap(err, "tagFile failed")
88+
}
89+
90+
bar.Increment()
91+
}
92+
}
93+
return nil
94+
}
95+
96+
func tagFile(path string, artist, title, year, album string, trackNum, tracksTotal int, cdNum int64) error {
97+
tag, err := id3v2.Open(path, id3v2.Options{Parse: true})
98+
if err != nil {
99+
return errors.Wrap(err, "id3v2 open failed")
100+
}
101+
defer tag.Close()
102+
103+
tag.SetArtist(artist)
104+
tag.SetTitle(title)
105+
tag.SetYear(year)
106+
tag.SetAlbum(album)
107+
108+
trckFrame := id3v2.TextFrame{
109+
Encoding: id3v2.ENUTF8,
110+
Text: fmt.Sprintf("%d/%d", trackNum, tracksTotal),
111+
}
112+
tag.AddFrame(tag.CommonID("TRCK"), trckFrame)
113+
114+
if cdNum >= 0 {
115+
tposFrame := id3v2.TextFrame{
116+
Encoding: id3v2.ENUTF8,
117+
Text: fmt.Sprintf("%d", cdNum),
118+
}
119+
tag.AddFrame(tag.CommonID("TPOS"), tposFrame)
120+
}
121+
122+
// Write it to file.
123+
if err = tag.Save(); err != nil {
124+
return errors.Wrap(err, "tag save failed")
125+
}
126+
127+
return nil
128+
}

main.go

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"path/filepath"
7+
8+
"os"
9+
10+
"github.com/michiwend/gomusicbrainz"
11+
homedir "github.com/mitchellh/go-homedir"
12+
"github.com/otium/ytdl"
13+
"github.com/pkg/errors"
14+
"github.com/subosito/norma"
15+
)
16+
17+
func handleError(err error) {
18+
var e error
19+
if err != nil {
20+
if err != errNoRelease {
21+
e = errors.WithStack(err)
22+
fmt.Printf("%+v", e)
23+
} else {
24+
e = err
25+
fmt.Printf("%s\n", e.Error())
26+
}
27+
os.Exit(1)
28+
}
29+
}
30+
31+
func dlRelease(library, url string, client *gomusicbrainz.WS2Client, vid *ytdl.VideoInfo) error {
32+
mbr, err := getAlbumInfo(client, vid.Title)
33+
handleError(err)
34+
35+
dlFolder := filepath.Join(library, norma.Sanitize(mbr.artist), norma.Sanitize(mbr.title))
36+
dlFile := filepath.Join(dlFolder, norma.Sanitize(mbr.title))
37+
defer os.Remove(dlFile)
38+
39+
tracks, err := getTracks(url)
40+
handleError(err)
41+
42+
fmt.Println("\nDownloading Video:")
43+
err = download(vid, dlFile)
44+
handleError(err)
45+
46+
fmt.Println("\nExtracting tracks:")
47+
return extractTracks(dlFile, mbr.year, mbr.title, tracks, mbr.tracks, dlFolder)
48+
}
49+
50+
func dlRecord(library string, client *gomusicbrainz.WS2Client, vid *ytdl.VideoInfo) error {
51+
mbr, err := getTrackInfo(client, vid.Title)
52+
handleError(err)
53+
54+
dlFolder := filepath.Join(library, norma.Sanitize(mbr.artist), norma.Sanitize(mbr.albumTitle))
55+
dlFile := filepath.Join(dlFolder, norma.Sanitize(mbr.trackTitle))
56+
defer os.Remove(dlFile)
57+
58+
fmt.Println("\nDownloading Video:")
59+
err = download(vid, dlFile)
60+
handleError(err)
61+
62+
return convertTrack(dlFile, mbr, dlFolder)
63+
}
64+
65+
const appName = "ymdl"
66+
const version = "0.0.1-beta"
67+
const contactURL = "github.com/HeavyHorst/ymdl"
68+
69+
func main() {
70+
var defaultLibraryPath string
71+
homeDir, err := homedir.Dir()
72+
if err != nil {
73+
//couldn't find the home directory
74+
defaultLibraryPath = "Music"
75+
} else {
76+
defaultLibraryPath = filepath.Join(homeDir, "Music")
77+
}
78+
79+
dlTrack := flag.Bool("track", false, "download a single track from youtube")
80+
dlAlbum := flag.Bool("album", true, "download a complete album from youtube")
81+
printVersion := flag.Bool("version", false, "print the version and quit")
82+
libraryPath := flag.String("lib", defaultLibraryPath, "the path to your music library")
83+
84+
flag.Parse()
85+
86+
if *printVersion {
87+
fmt.Println(version)
88+
os.Exit(0)
89+
}
90+
91+
client, err := gomusicbrainz.NewWS2Client("https://musicbrainz.org/ws/2", appName, version, contactURL)
92+
handleError(err)
93+
94+
for _, url := range flag.Args() {
95+
vid, err := ytdl.GetVideoInfo(url)
96+
handleError(err)
97+
98+
if *dlTrack {
99+
handleError(dlRecord(*libraryPath, client, vid))
100+
} else if *dlAlbum {
101+
handleError(dlRelease(*libraryPath, url, client, vid))
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)