added readme
This commit is contained in:
parent
33bea9b20e
commit
ce1325606d
8 changed files with 163 additions and 26 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,3 +1 @@
|
||||||
podcasts.toml
|
podCatch
|
||||||
db/*
|
|
||||||
podcatch-bin
|
|
||||||
|
|
22
README.md
Normal file
22
README.md
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# PodCatch - a simple Podcast downloader
|
||||||
|
|
||||||
|
PodCatch is a _very_ simple Podcast downloader written in GoLand built to help me download podcasts
|
||||||
|
and store them in my media directory so [Jellyfin](https://jellyfin.org/) can
|
||||||
|
index them and thus can be listened to using Jellyfin's various apps.
|
||||||
|
Due to how PodCatch is configurable, I'm sure it would have other uses too.
|
||||||
|
|
||||||
|
### Why PodCatch over other podcast downloaders?
|
||||||
|
I tried a few other podcast downloaders and largely they were fine, in fact
|
||||||
|
PodCatch definitely borrows some nice ideas from other podcast downloaders. My
|
||||||
|
personal issue with the others is that the filename they download as is not always
|
||||||
|
"friendly"; that is to say, if there's no ID3 data containing the podcast title,
|
||||||
|
trying to organise a list of podcasts with a _GUID_.mp3 filename in Jellyfin is not easy.
|
||||||
|
To tackle this issue, PodCatch looks up the title of the Podcast from its RSS feed,
|
||||||
|
strips it of invalid characters, and uses that name as the file name. This allows for a
|
||||||
|
much easier task when organising podcasts.
|
||||||
|
|
||||||
|
### Install
|
||||||
|
Very simple install:
|
||||||
|
```git clone this repo
|
||||||
|
cd podcatch
|
||||||
|
go build -o podcatch-bin
|
8
defaults/podcasts.toml
Normal file
8
defaults/podcasts.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
[HelloInternet]
|
||||||
|
Name = "Hello Internet"
|
||||||
|
URL = "http://www.hellointernet.fm/podcast?format=rss"
|
||||||
|
|
||||||
|
[NSTAAF]
|
||||||
|
Name = "No Such Thing as a Fish"
|
||||||
|
URL = "https://audioboom.com/channels/2399216.rss"
|
2
defaults/settings.toml
Normal file
2
defaults/settings.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Directory = "~/podcasts/"
|
||||||
|
Limit = 10
|
2
go.mod
2
go.mod
|
@ -1,4 +1,4 @@
|
||||||
module podcatch
|
module podCatch
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
|
|
2
main.go
2
main.go
|
@ -1,6 +1,6 @@
|
||||||
package main
|
package main
|
||||||
import (
|
import (
|
||||||
"podcatch/podcatch"
|
"podCatch/podcatch"
|
||||||
)
|
)
|
||||||
func main(){
|
func main(){
|
||||||
podcatch.Start()
|
podcatch.Start()
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package podcatch
|
package podcatch
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
. "podcatch/structs"
|
. "podCatch/structs"
|
||||||
"github.com/pelletier/go-toml"
|
"github.com/pelletier/go-toml"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -15,13 +16,40 @@ var Version string = "0.1"
|
||||||
var Settings Settings
|
var Settings Settings
|
||||||
var Podcasts map[string]Podcast = make(map[string]Podcast)
|
var Podcasts map[string]Podcast = make(map[string]Podcast)
|
||||||
var donefile string
|
var donefile string
|
||||||
|
var podcatchdir string
|
||||||
|
var homedir string
|
||||||
|
var dbdir string
|
||||||
func Start(){
|
func Start(){
|
||||||
fmt.Printf("Starting PodCatch Version : %s...\r\n", Version )
|
fmt.Printf("Starting PodCatch Version : %s...\r\n", Version )
|
||||||
|
getHomeDirs()
|
||||||
getSettings()
|
getSettings()
|
||||||
getPodcasts()
|
getPodcasts()
|
||||||
}
|
}
|
||||||
|
func getHomeDirs(){
|
||||||
|
h, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal( err )
|
||||||
|
}
|
||||||
|
homedir = h
|
||||||
|
podcatchdir = h + "/.podcatch/"
|
||||||
|
}
|
||||||
func getSettings(){
|
func getSettings(){
|
||||||
content, err := ioutil.ReadFile("settings.toml")
|
settings := podcatchdir + "settings.toml"
|
||||||
|
if !checkFileExists(settings){
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Copying default settings.toml to user dir.")
|
||||||
|
ok, err := copyFile(pwd + "/defaults/settings.toml",settings)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if ok > 0 {
|
||||||
|
fmt.Println("Copied.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content, err := ioutil.ReadFile(settings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -29,6 +57,10 @@ func getSettings(){
|
||||||
if e != nil {
|
if e != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
Settings.Directory = strings.Replace(Settings.Directory,"~",homedir,1)
|
||||||
|
dbdir = Settings.Directory + ".db/"
|
||||||
|
os.Mkdir(dbdir,0755)
|
||||||
|
|
||||||
}
|
}
|
||||||
func getPodcasts(){
|
func getPodcasts(){
|
||||||
if len(Podcasts) == 0 {
|
if len(Podcasts) == 0 {
|
||||||
|
@ -42,7 +74,22 @@ func getPodcasts(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func getPodcastFiles() {
|
func getPodcastFiles() {
|
||||||
content, err := ioutil.ReadFile("podcasts.toml")
|
pcs := podcatchdir + "podcasts.toml"
|
||||||
|
if !checkFileExists(pcs){
|
||||||
|
pwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Copying default podcasts.toml to user dir.")
|
||||||
|
ok, err := copyFile(pwd + "/defaults/podcasts.toml",pcs)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if ok > 0 {
|
||||||
|
fmt.Println("Copied.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content, err := ioutil.ReadFile(podcatchdir + "podcasts.toml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -101,8 +148,10 @@ func downloadCasts(podcast Podcast) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func podcastDownloaded(item Item) bool {
|
func podcastDownloaded(item Item) bool {
|
||||||
|
db := dbdir + "complete"
|
||||||
|
if checkCreate(db) {
|
||||||
if len(donefile) < 1 {
|
if len(donefile) < 1 {
|
||||||
content, err := ioutil.ReadFile(".db/complete")
|
content, err := ioutil.ReadFile(db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -114,6 +163,7 @@ func podcastDownloaded(item Item) bool {
|
||||||
if strings.Contains(donefile,item.Media.URL){
|
if strings.Contains(donefile,item.Media.URL){
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func downloadMp3(url string, file string) bool {
|
func downloadMp3(url string, file string) bool {
|
||||||
|
@ -150,8 +200,9 @@ func createNFO(item Item, file string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func markAsReceived(item Item) {
|
func markAsReceived(item Item) {
|
||||||
os.Mkdir(".db", 0777)
|
db := dbdir + "complete"
|
||||||
file, err := os.OpenFile(".db/complete", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0755)
|
checkCreate(db)
|
||||||
|
file, err := os.OpenFile(db, os.O_APPEND|os.O_WRONLY, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
@ -162,8 +213,9 @@ func markAsReceived(item Item) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func markAsErrored(item Item) {
|
func markAsErrored(item Item) {
|
||||||
os.Mkdir(".db", 0777)
|
db := dbdir + "error"
|
||||||
file, err := os.OpenFile(".db/error", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0755)
|
checkCreate(db)
|
||||||
|
file, err := os.OpenFile(db, os.O_APPEND|os.O_WRONLY, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
@ -173,3 +225,60 @@ func markAsErrored(item Item) {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func checkFileExists(file string) bool {
|
||||||
|
if _, err := os.Stat(file); err == nil {
|
||||||
|
// fmt.Println("Exists")
|
||||||
|
// exists
|
||||||
|
return true
|
||||||
|
} else if os.IsNotExist(err) {
|
||||||
|
// fmt.Println("Not Exists")
|
||||||
|
// not exists
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
// fmt.Println("Maybe Exists, Maybe Not")
|
||||||
|
return false
|
||||||
|
// Schrodinger: file may or may not exist. See err for details.
|
||||||
|
// Therefore, do *NOT* use !os.IsNotExist(err) to test for file existence
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func checkCreate(file string) bool {
|
||||||
|
if checkFileExists(file) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
if createFile(file) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func createFile(file string) bool {
|
||||||
|
f, err := os.Create(file)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func copyFile(src, dst string) (int64, error) {
|
||||||
|
sourceFileStat, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if !sourceFileStat.Mode().IsRegular() {
|
||||||
|
return 0, fmt.Errorf("%s is not a regular file", src)
|
||||||
|
}
|
||||||
|
source, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer source.Close()
|
||||||
|
destination, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer destination.Close()
|
||||||
|
nBytes, err := io.Copy(destination, source)
|
||||||
|
return nBytes, err
|
||||||
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
Directory = "/home/psw/podcasts/"
|
|
||||||
Limit = 50
|
|
Loading…
Reference in a new issue