mirror of
https://github.com/crunchy-labs/crunchy-cli.git
synced 2026-01-21 12:12:00 -06:00
Fix typos & add more comments
This commit is contained in:
parent
2e9ce3cf52
commit
3617955bc5
16 changed files with 82 additions and 68 deletions
|
|
@ -73,7 +73,7 @@ _Before reading_: Because of the huge functionality not all cases can be covered
|
||||||
|
|
||||||
### Login
|
### Login
|
||||||
|
|
||||||
Before you can do something, you have to login first.
|
Before you can do something, you have to log in first.
|
||||||
|
|
||||||
This can be performed via crunchyroll account email and password.
|
This can be performed via crunchyroll account email and password.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -728,7 +728,7 @@ func (tc *tarCompress) Close() error {
|
||||||
err = tc.dst.Close()
|
err = tc.dst.Close()
|
||||||
|
|
||||||
if err != nil && err2 != nil {
|
if err != nil && err2 != nil {
|
||||||
// best way to show double errors at once that i've found
|
// best way to show double errors at once that I've found
|
||||||
return fmt.Errorf("%v\n%v", err, err2)
|
return fmt.Errorf("%v\n%v", err, err2)
|
||||||
} else if err == nil && err2 != nil {
|
} else if err == nil && err2 != nil {
|
||||||
err = err2
|
err = err2
|
||||||
|
|
@ -750,11 +750,11 @@ func (tc *tarCompress) NewFile(information formatInformation) (io.WriteCloser, e
|
||||||
ModTime: time.Now(),
|
ModTime: time.Now(),
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
Typeflag: tar.TypeReg,
|
Typeflag: tar.TypeReg,
|
||||||
// fun fact: i did not set the size for quiet some time because i thought that it isn't
|
// fun fact: I did not set the size for quiet some time because I thought that it isn't
|
||||||
// required. well because of this i debugged this part for multiple hours because without
|
// required. well because of this I debugged this part for multiple hours because without
|
||||||
// proper size information only a tiny amount gets copied into the tar (or zip) writer.
|
// proper size information only a tiny amount gets copied into the tar (or zip) writer.
|
||||||
// this is also the reason why the file content is completely copied into a buffer before
|
// this is also the reason why the file content is completely copied into a buffer before
|
||||||
// writing it to the writer. i could bypass this and save some memory but this requires
|
// writing it to the writer. I could bypass this and save some memory but this requires
|
||||||
// some rewriting and im nearly at the (planned) finish for version 2 so nah in the future
|
// some rewriting and im nearly at the (planned) finish for version 2 so nah in the future
|
||||||
// maybe
|
// maybe
|
||||||
Size: int64(buf.Len()),
|
Size: int64(buf.Len()),
|
||||||
|
|
|
||||||
|
|
@ -403,7 +403,7 @@ func (dp *downloadProgress) update(msg string, permanent bool) {
|
||||||
pre := fmt.Sprintf("%s%s [", dp.Prefix, msg)
|
pre := fmt.Sprintf("%s%s [", dp.Prefix, msg)
|
||||||
post := fmt.Sprintf("]%4d%% %8d/%d", int(percentage), dp.Current, dp.Total)
|
post := fmt.Sprintf("]%4d%% %8d/%d", int(percentage), dp.Current, dp.Total)
|
||||||
|
|
||||||
// i don't really know why +2 is needed here but without it the Printf below would not print to the line end
|
// I don't really know why +2 is needed here but without it the Printf below would not print to the line end
|
||||||
progressWidth := terminalWidth() - len(pre) - len(post) + 2
|
progressWidth := terminalWidth() - len(pre) - len(post) + 2
|
||||||
repeatCount := int(percentage / float32(100) * float32(progressWidth))
|
repeatCount := int(percentage / float32(100) * float32(progressWidth))
|
||||||
// it can be lower than zero when the terminal is very tiny
|
// it can be lower than zero when the terminal is very tiny
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LOCALE represents a locale / language
|
// LOCALE represents a locale / language.
|
||||||
type LOCALE string
|
type LOCALE string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -31,16 +31,16 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Crunchyroll struct {
|
type Crunchyroll struct {
|
||||||
// Client is the http.Client to perform all requests over
|
// Client is the http.Client to perform all requests over.
|
||||||
Client *http.Client
|
Client *http.Client
|
||||||
// Context can be used to stop requests with Client and is context.Background by default
|
// Context can be used to stop requests with Client and is context.Background by default.
|
||||||
Context context.Context
|
Context context.Context
|
||||||
// Locale specifies in which language all results should be returned / requested
|
// Locale specifies in which language all results should be returned / requested.
|
||||||
Locale LOCALE
|
Locale LOCALE
|
||||||
// SessionID is the crunchyroll session id which was used for authentication
|
// SessionID is the crunchyroll session id which was used for authentication.
|
||||||
SessionID string
|
SessionID string
|
||||||
|
|
||||||
// Config stores parameters which are needed by some api calls
|
// Config stores parameters which are needed by some api calls.
|
||||||
Config struct {
|
Config struct {
|
||||||
TokenType string
|
TokenType string
|
||||||
AccessToken string
|
AccessToken string
|
||||||
|
|
@ -56,11 +56,11 @@ type Crunchyroll struct {
|
||||||
MaturityRating string
|
MaturityRating string
|
||||||
}
|
}
|
||||||
|
|
||||||
// If cache is true, internal caching is enabled
|
// If cache is true, internal caching is enabled.
|
||||||
cache bool
|
cache bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginWithCredentials logs in via crunchyroll username or email and password
|
// LoginWithCredentials logs in via crunchyroll username or email and password.
|
||||||
func LoginWithCredentials(user string, password string, locale LOCALE, client *http.Client) (*Crunchyroll, error) {
|
func LoginWithCredentials(user string, password string, locale LOCALE, client *http.Client) (*Crunchyroll, error) {
|
||||||
sessionIDEndpoint := fmt.Sprintf("https://api.crunchyroll.com/start_session.0.json?version=1.0&access_token=%s&device_type=%s&device_id=%s",
|
sessionIDEndpoint := fmt.Sprintf("https://api.crunchyroll.com/start_session.0.json?version=1.0&access_token=%s&device_type=%s&device_id=%s",
|
||||||
"LNDJgOit5yaRIWN", "com.crunchyroll.windows.desktop", "Az2srGnChW65fuxYz2Xxl1GcZQgtGgI")
|
"LNDJgOit5yaRIWN", "com.crunchyroll.windows.desktop", "Az2srGnChW65fuxYz2Xxl1GcZQgtGgI")
|
||||||
|
|
@ -87,7 +87,7 @@ func LoginWithCredentials(user string, password string, locale LOCALE, client *h
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginWithSessionID logs in via a crunchyroll session id.
|
// LoginWithSessionID logs in via a crunchyroll session id.
|
||||||
// Session ids are automatically generated as a cookie when visiting https://www.crunchyroll.com
|
// Session ids are automatically generated as a cookie when visiting https://www.crunchyroll.com.
|
||||||
func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (*Crunchyroll, error) {
|
func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (*Crunchyroll, error) {
|
||||||
crunchy := &Crunchyroll{
|
crunchy := &Crunchyroll{
|
||||||
Client: client,
|
Client: client,
|
||||||
|
|
@ -205,7 +205,7 @@ func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (*
|
||||||
return crunchy, nil
|
return crunchy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// request is a base function which handles api requests
|
// request is a base function which handles api requests.
|
||||||
func (c *Crunchyroll) request(endpoint string) (*http.Response, error) {
|
func (c *Crunchyroll) request(endpoint string) (*http.Response, error) {
|
||||||
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
|
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -241,7 +241,7 @@ func (c *Crunchyroll) request(endpoint string) (*http.Response, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsCaching returns if data gets cached or not.
|
// IsCaching returns if data gets cached or not.
|
||||||
// See SetCaching for more information
|
// See SetCaching for more information.
|
||||||
func (c *Crunchyroll) IsCaching() bool {
|
func (c *Crunchyroll) IsCaching() bool {
|
||||||
return c.cache
|
return c.cache
|
||||||
}
|
}
|
||||||
|
|
@ -249,12 +249,12 @@ func (c *Crunchyroll) IsCaching() bool {
|
||||||
// SetCaching enables or disables internal caching of requests made.
|
// SetCaching enables or disables internal caching of requests made.
|
||||||
// Caching is enabled by default.
|
// Caching is enabled by default.
|
||||||
// If it is disabled the already cached data still gets called.
|
// If it is disabled the already cached data still gets called.
|
||||||
// The best way to prevent this is to create a complete new Crunchyroll struct
|
// The best way to prevent this is to create a complete new Crunchyroll struct.
|
||||||
func (c *Crunchyroll) SetCaching(caching bool) {
|
func (c *Crunchyroll) SetCaching(caching bool) {
|
||||||
c.cache = caching
|
c.cache = caching
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search searches a query and returns all found series and movies within the given limit
|
// Search searches a query and returns all found series and movies within the given limit.
|
||||||
func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, err error) {
|
func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, err error) {
|
||||||
searchEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/search?q=%s&n=%d&type=&locale=%s",
|
searchEndpoint := fmt.Sprintf("https://beta-api.crunchyroll.com/content/v1/search?q=%s&n=%d&type=&locale=%s",
|
||||||
query, limit, c.Locale)
|
query, limit, c.Locale)
|
||||||
|
|
@ -397,7 +397,7 @@ func ParseEpisodeURL(url string) (seriesName, title string, episodeNumber int, w
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseBetaSeriesURL tries to extract the season id of the given crunchyroll beta url, pointing to a season
|
// ParseBetaSeriesURL tries to extract the season id of the given crunchyroll beta url, pointing to a season.
|
||||||
func ParseBetaSeriesURL(url string) (seasonId string, ok bool) {
|
func ParseBetaSeriesURL(url string) (seasonId string, ok bool) {
|
||||||
pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?series/(?P<seasonId>\w+).*`)
|
pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?series/(?P<seasonId>\w+).*`)
|
||||||
if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 {
|
if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 {
|
||||||
|
|
@ -408,7 +408,7 @@ func ParseBetaSeriesURL(url string) (seasonId string, ok bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseBetaEpisodeURL tries to extract the episode id of the given crunchyroll beta url, pointing to an episode
|
// ParseBetaEpisodeURL tries to extract the episode id of the given crunchyroll beta url, pointing to an episode.
|
||||||
func ParseBetaEpisodeURL(url string) (episodeId string, ok bool) {
|
func ParseBetaEpisodeURL(url string) (episodeId string, ok bool) {
|
||||||
pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?watch/(?P<episodeId>\w+).*`)
|
pattern := regexp.MustCompile(`(?m)^https?://(www\.)?beta\.crunchyroll\.com/(\w{2}/)?watch/(?P<episodeId>\w+).*`)
|
||||||
if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 {
|
if urlMatch := pattern.FindAllStringSubmatch(url, -1); len(urlMatch) != 0 {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewDownloader creates a downloader with default settings which should
|
// NewDownloader creates a downloader with default settings which should
|
||||||
// fit the most needs
|
// fit the most needs.
|
||||||
func NewDownloader(context context.Context, writer io.Writer, goroutines int, onSegmentDownload func(segment *m3u8.MediaSegment, current, total int, file *os.File) error) Downloader {
|
func NewDownloader(context context.Context, writer io.Writer, goroutines int, onSegmentDownload func(segment *m3u8.MediaSegment, current, total int, file *os.File) error) Downloader {
|
||||||
tmp, _ := os.MkdirTemp("", "crunchy_")
|
tmp, _ := os.MkdirTemp("", "crunchy_")
|
||||||
|
|
||||||
|
|
@ -43,12 +43,12 @@ type Downloader struct {
|
||||||
// The files will be placed directly into the root of the directory.
|
// The files will be placed directly into the root of the directory.
|
||||||
// If empty a random temporary directory on the system's default tempdir
|
// If empty a random temporary directory on the system's default tempdir
|
||||||
// will be created.
|
// will be created.
|
||||||
// If the directory does not exist, it will be created
|
// If the directory does not exist, it will be created.
|
||||||
TempDir string
|
TempDir string
|
||||||
// If DeleteTempAfter is true, the temp directory gets deleted afterwards.
|
// If DeleteTempAfter is true, the temp directory gets deleted afterwards.
|
||||||
// Note that in case of a hard signal exit (os.Interrupt, ...) the directory
|
// Note that in case of a hard signal exit (os.Interrupt, ...) the directory
|
||||||
// will NOT be deleted. In such situations try to catch the signal and
|
// will NOT be deleted. In such situations try to catch the signal and
|
||||||
// cancel Context
|
// cancel Context.
|
||||||
DeleteTempAfter bool
|
DeleteTempAfter bool
|
||||||
|
|
||||||
// Context to control the download process with.
|
// Context to control the download process with.
|
||||||
|
|
@ -56,7 +56,7 @@ type Downloader struct {
|
||||||
// process. So it is not recommend stopping the program immediately after calling
|
// process. So it is not recommend stopping the program immediately after calling
|
||||||
// the cancel function. It's better when canceling it and then exit the program
|
// the cancel function. It's better when canceling it and then exit the program
|
||||||
// when Format.Download throws an error. See the signal handling section in
|
// when Format.Download throws an error. See the signal handling section in
|
||||||
// cmd/crunchyroll-go/cmd/download.go for an example
|
// cmd/crunchyroll-go/cmd/download.go for an example.
|
||||||
Context context.Context
|
Context context.Context
|
||||||
|
|
||||||
// Goroutines is the number of goroutines to download segments with
|
// Goroutines is the number of goroutines to download segments with
|
||||||
|
|
@ -65,11 +65,11 @@ type Downloader struct {
|
||||||
// A method to call when a segment was downloaded.
|
// A method to call when a segment was downloaded.
|
||||||
// Note that the segments are downloaded asynchronously (depending on the count of
|
// Note that the segments are downloaded asynchronously (depending on the count of
|
||||||
// Goroutines) and the function gets called asynchronously too, so for example it is
|
// Goroutines) and the function gets called asynchronously too, so for example it is
|
||||||
// first called on segment 1, then segment 254, then segment 3 and so on
|
// first called on segment 1, then segment 254, then segment 3 and so on.
|
||||||
OnSegmentDownload func(segment *m3u8.MediaSegment, current, total int, file *os.File) error
|
OnSegmentDownload func(segment *m3u8.MediaSegment, current, total int, file *os.File) error
|
||||||
// If LockOnSegmentDownload is true, only one OnSegmentDownload function can be called at
|
// If LockOnSegmentDownload is true, only one OnSegmentDownload function can be called at
|
||||||
// once. Normally (because of the use of goroutines while downloading) multiple could get
|
// once. Normally (because of the use of goroutines while downloading) multiple could get
|
||||||
// called simultaneously
|
// called simultaneously.
|
||||||
LockOnSegmentDownload bool
|
LockOnSegmentDownload bool
|
||||||
|
|
||||||
// If FFmpegOpts is not nil, ffmpeg will be used to merge and convert files.
|
// If FFmpegOpts is not nil, ffmpeg will be used to merge and convert files.
|
||||||
|
|
@ -82,7 +82,7 @@ type Downloader struct {
|
||||||
FFmpegOpts []string
|
FFmpegOpts []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// download's the given format
|
// download's the given format.
|
||||||
func (d Downloader) download(format *Format) error {
|
func (d Downloader) download(format *Format) error {
|
||||||
if err := format.InitVideo(); err != nil {
|
if err := format.InitVideo(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -109,7 +109,7 @@ func (d Downloader) download(format *Format) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeSegments reads every file in tempDir and writes their content to Downloader.Writer.
|
// mergeSegments reads every file in tempDir and writes their content to Downloader.Writer.
|
||||||
// The given output file gets created or overwritten if already existing
|
// The given output file gets created or overwritten if already existing.
|
||||||
func (d Downloader) mergeSegments(files []string) error {
|
func (d Downloader) mergeSegments(files []string) error {
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
select {
|
select {
|
||||||
|
|
@ -132,7 +132,7 @@ func (d Downloader) mergeSegments(files []string) error {
|
||||||
|
|
||||||
// mergeSegmentsFFmpeg reads every file in tempDir and merges their content to the outputFile
|
// mergeSegmentsFFmpeg reads every file in tempDir and merges their content to the outputFile
|
||||||
// with ffmpeg (https://ffmpeg.org/).
|
// with ffmpeg (https://ffmpeg.org/).
|
||||||
// The given output file gets created or overwritten if already existing
|
// The given output file gets created or overwritten if already existing.
|
||||||
func (d Downloader) mergeSegmentsFFmpeg(files []string) error {
|
func (d Downloader) mergeSegmentsFFmpeg(files []string) error {
|
||||||
list, err := os.Create(filepath.Join(d.TempDir, "list.txt"))
|
list, err := os.Create(filepath.Join(d.TempDir, "list.txt"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -214,13 +214,13 @@ func (d Downloader) mergeSegmentsFFmpeg(files []string) error {
|
||||||
// After every segment download onSegmentDownload will be called with:
|
// After every segment download onSegmentDownload will be called with:
|
||||||
// the downloaded segment, the current position, the total size of segments to download,
|
// the downloaded segment, the current position, the total size of segments to download,
|
||||||
// the file where the segment content was written to an error (if occurred).
|
// the file where the segment content was written to an error (if occurred).
|
||||||
// The filename is always <number of downloaded segment>.ts
|
// The filename is always <number of downloaded segment>.ts.
|
||||||
//
|
//
|
||||||
// Short explanation:
|
// Short explanation:
|
||||||
// The actual crunchyroll video is split up in multiple segments (or video files) which
|
// The actual crunchyroll video is split up in multiple segments (or video files) which
|
||||||
// have to be downloaded and merged after to generate a single video file.
|
// have to be downloaded and merged after to generate a single video file.
|
||||||
// And this function just downloads each of this segment into the given directory.
|
// And this function just downloads each of this segment into the given directory.
|
||||||
// See https://en.wikipedia.org/wiki/MPEG_transport_stream for more information
|
// See https://en.wikipedia.org/wiki/MPEG_transport_stream for more information.
|
||||||
func (d Downloader) downloadSegments(format *Format) ([]string, error) {
|
func (d Downloader) downloadSegments(format *Format) ([]string, error) {
|
||||||
if err := format.InitVideo(); err != nil {
|
if err := format.InitVideo(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -318,7 +318,7 @@ func (d Downloader) downloadSegments(format *Format) ([]string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCrypt extracts the key and iv of a m3u8 segment and converts it into a cipher.Block and an iv byte sequence
|
// getCrypt extracts the key and iv of a m3u8 segment and converts it into a cipher.Block and an iv byte sequence.
|
||||||
func getCrypt(format *Format, segment *m3u8.MediaSegment) (block cipher.Block, iv []byte, err error) {
|
func getCrypt(format *Format, segment *m3u8.MediaSegment) (block cipher.Block, iv []byte, err error) {
|
||||||
var resp *http.Response
|
var resp *http.Response
|
||||||
|
|
||||||
|
|
@ -341,7 +341,7 @@ func getCrypt(format *Format, segment *m3u8.MediaSegment) (block cipher.Block, i
|
||||||
return block, iv, nil
|
return block, iv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadSegment downloads a segment, decrypts it and names it after the given index
|
// downloadSegment downloads a segment, decrypts it and names it after the given index.
|
||||||
func (d Downloader) downloadSegment(format *Format, segment *m3u8.MediaSegment, filename string, block cipher.Block, iv []byte) (*os.File, error) {
|
func (d Downloader) downloadSegment(format *Format, segment *m3u8.MediaSegment, filename string, block cipher.Block, iv []byte) (*os.File, error) {
|
||||||
// every segment is aes-128 encrypted and has to be decrypted when downloaded
|
// every segment is aes-128 encrypted and has to be decrypted when downloaded
|
||||||
content, err := d.decryptSegment(format.crunchy.Client, segment, block, iv)
|
content, err := d.decryptSegment(format.crunchy.Client, segment, block, iv)
|
||||||
|
|
@ -361,7 +361,7 @@ func (d Downloader) downloadSegment(format *Format, segment *m3u8.MediaSegment,
|
||||||
return file, nil
|
return file, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L25
|
// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L25.
|
||||||
func (d Downloader) decryptSegment(client *http.Client, segment *m3u8.MediaSegment, block cipher.Block, iv []byte) ([]byte, error) {
|
func (d Downloader) decryptSegment(client *http.Client, segment *m3u8.MediaSegment, block cipher.Block, iv []byte) ([]byte, error) {
|
||||||
req, err := http.NewRequestWithContext(d.Context, http.MethodGet, segment.URI, nil)
|
req, err := http.NewRequestWithContext(d.Context, http.MethodGet, segment.URI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -387,7 +387,7 @@ func (d Downloader) decryptSegment(client *http.Client, segment *m3u8.MediaSegme
|
||||||
return raw, nil
|
return raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L47
|
// https://github.com/oopsguy/m3u8/blob/4150e93ec8f4f8718875a02973f5d792648ecb97/tool/crypt.go#L47.
|
||||||
func (d Downloader) pkcs5UnPadding(origData []byte) []byte {
|
func (d Downloader) pkcs5UnPadding(origData []byte) []byte {
|
||||||
length := len(origData)
|
length := len(origData)
|
||||||
unPadding := int(origData[length-1])
|
unPadding := int(origData[length-1])
|
||||||
|
|
|
||||||
10
episode.go
10
episode.go
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Episode contains all information about an episode.
|
||||||
type Episode struct {
|
type Episode struct {
|
||||||
crunchy *Crunchyroll
|
crunchy *Crunchyroll
|
||||||
|
|
||||||
|
|
@ -74,7 +75,7 @@ type Episode struct {
|
||||||
StreamID string
|
StreamID string
|
||||||
}
|
}
|
||||||
|
|
||||||
// EpisodeFromID returns an episode by its api id
|
// EpisodeFromID returns an episode by its api id.
|
||||||
func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) {
|
func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) {
|
||||||
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/episodes/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/episodes/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||||
crunchy.Config.CountryCode,
|
crunchy.Config.CountryCode,
|
||||||
|
|
@ -111,7 +112,8 @@ func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error) {
|
||||||
|
|
||||||
// AudioLocale returns the audio locale of the episode.
|
// AudioLocale returns the audio locale of the episode.
|
||||||
// Every episode in a season (should) have the same audio locale,
|
// Every episode in a season (should) have the same audio locale,
|
||||||
// so if you want to get the audio locale of a season, just call this method on the first episode of the season
|
// so if you want to get the audio locale of a season, just call
|
||||||
|
// this method on the first episode of the season.
|
||||||
func (e *Episode) AudioLocale() (LOCALE, error) {
|
func (e *Episode) AudioLocale() (LOCALE, error) {
|
||||||
streams, err := e.Streams()
|
streams, err := e.Streams()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -120,7 +122,7 @@ func (e *Episode) AudioLocale() (LOCALE, error) {
|
||||||
return streams[0].AudioLocale, nil
|
return streams[0].AudioLocale, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFormat returns the format which matches the given resolution and subtitle locale
|
// GetFormat returns the format which matches the given resolution and subtitle locale.
|
||||||
func (e *Episode) GetFormat(resolution string, subtitle LOCALE, hardsub bool) (*Format, error) {
|
func (e *Episode) GetFormat(resolution string, subtitle LOCALE, hardsub bool) (*Format, error) {
|
||||||
streams, err := e.Streams()
|
streams, err := e.Streams()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -186,7 +188,7 @@ func (e *Episode) GetFormat(resolution string, subtitle LOCALE, hardsub bool) (*
|
||||||
return nil, fmt.Errorf("no matching resolution found")
|
return nil, fmt.Errorf("no matching resolution found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Streams returns all streams which are available for the episode
|
// Streams returns all streams which are available for the episode.
|
||||||
func (e *Episode) Streams() ([]*Stream, error) {
|
func (e *Episode) Streams() ([]*Stream, error) {
|
||||||
if e.children != nil {
|
if e.children != nil {
|
||||||
return e.children, nil
|
return e.children, nil
|
||||||
|
|
|
||||||
2
error.go
2
error.go
|
|
@ -3,7 +3,7 @@ package crunchyroll
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// AccessError is an error which will be returned when some special sort of api request fails.
|
// AccessError is an error which will be returned when some special sort of api request fails.
|
||||||
// See Crunchyroll.request when the error gets used
|
// See Crunchyroll.request when the error gets used.
|
||||||
type AccessError struct {
|
type AccessError struct {
|
||||||
error
|
error
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@ const (
|
||||||
MOVIE = "movies"
|
MOVIE = "movies"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Format contains detailed information about an episode video stream.
|
||||||
type Format struct {
|
type Format struct {
|
||||||
crunchy *Crunchyroll
|
crunchy *Crunchyroll
|
||||||
|
|
||||||
ID string
|
ID string
|
||||||
// FormatType represents if the format parent is an episode or a movie
|
// FormatType represents if the format parent is an episode or a movie.
|
||||||
FormatType FormatType
|
FormatType FormatType
|
||||||
Video *m3u8.Variant
|
Video *m3u8.Variant
|
||||||
AudioLocale LOCALE
|
AudioLocale LOCALE
|
||||||
|
|
@ -27,7 +28,7 @@ type Format struct {
|
||||||
// The Format.Video.Chunklist pointer is, by default, nil because an additional
|
// The Format.Video.Chunklist pointer is, by default, nil because an additional
|
||||||
// request must be made to receive its content. The request is not made when
|
// request must be made to receive its content. The request is not made when
|
||||||
// initializing a Format struct because it would probably cause an intense overhead
|
// initializing a Format struct because it would probably cause an intense overhead
|
||||||
// since Format.Video.Chunklist is only used sometimes
|
// since Format.Video.Chunklist is only used sometimes.
|
||||||
func (f *Format) InitVideo() error {
|
func (f *Format) InitVideo() error {
|
||||||
if f.Video.Chunklist == nil {
|
if f.Video.Chunklist == nil {
|
||||||
resp, err := f.crunchy.Client.Get(f.Video.URI)
|
resp, err := f.crunchy.Client.Get(f.Video.URI)
|
||||||
|
|
@ -45,7 +46,7 @@ func (f *Format) InitVideo() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download downloads the Format with the via Downloader specified options
|
// Download downloads the Format with the via Downloader specified options.
|
||||||
func (f *Format) Download(downloader Downloader) error {
|
func (f *Format) Download(downloader Downloader) error {
|
||||||
return downloader.download(f)
|
return downloader.download(f)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MovieListing contains information about something which is called
|
||||||
|
// movie listing. I don't know what this means thb.
|
||||||
type MovieListing struct {
|
type MovieListing struct {
|
||||||
crunchy *Crunchyroll
|
crunchy *Crunchyroll
|
||||||
|
|
||||||
|
|
@ -36,7 +38,7 @@ type MovieListing struct {
|
||||||
AvailabilityNotes string `json:"availability_notes"`
|
AvailabilityNotes string `json:"availability_notes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MovieListingFromID returns a movie listing by its api id
|
// MovieListingFromID returns a movie listing by its api id.
|
||||||
func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error) {
|
func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error) {
|
||||||
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movie_listing/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movie_listing/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||||
crunchy.Config.CountryCode,
|
crunchy.Config.CountryCode,
|
||||||
|
|
@ -65,7 +67,7 @@ func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error)
|
||||||
return movieListing, nil
|
return movieListing, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AudioLocale is same as Episode.AudioLocale
|
// AudioLocale is same as Episode.AudioLocale.
|
||||||
func (ml *MovieListing) AudioLocale() (LOCALE, error) {
|
func (ml *MovieListing) AudioLocale() (LOCALE, error) {
|
||||||
resp, err := ml.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
resp, err := ml.crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||||
ml.crunchy.Config.CountryCode,
|
ml.crunchy.Config.CountryCode,
|
||||||
|
|
@ -86,7 +88,7 @@ func (ml *MovieListing) AudioLocale() (LOCALE, error) {
|
||||||
return LOCALE(jsonBody["audio_locale"].(string)), nil
|
return LOCALE(jsonBody["audio_locale"].(string)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Streams returns all streams which are available for the movie listing
|
// Streams returns all streams which are available for the movie listing.
|
||||||
func (ml *MovieListing) Streams() ([]*Stream, error) {
|
func (ml *MovieListing) Streams() ([]*Stream, error) {
|
||||||
return fromVideoStreams(ml.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
return fromVideoStreams(ml.crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||||
ml.crunchy.Config.CountryCode,
|
ml.crunchy.Config.CountryCode,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Season contains information about an anime season.
|
||||||
type Season struct {
|
type Season struct {
|
||||||
crunchy *Crunchyroll
|
crunchy *Crunchyroll
|
||||||
|
|
||||||
|
|
@ -41,7 +42,7 @@ type Season struct {
|
||||||
SubtitleLocales []LOCALE
|
SubtitleLocales []LOCALE
|
||||||
}
|
}
|
||||||
|
|
||||||
// SeasonFromID returns a season by its api id
|
// SeasonFromID returns a season by its api id.
|
||||||
func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) {
|
func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) {
|
||||||
resp, err := crunchy.Client.Get(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/seasons/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
resp, err := crunchy.Client.Get(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/seasons/%s?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||||
crunchy.Config.CountryCode,
|
crunchy.Config.CountryCode,
|
||||||
|
|
@ -70,6 +71,7 @@ func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error) {
|
||||||
return season, nil
|
return season, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AudioLocale returns the audio locale of the season.
|
||||||
func (s *Season) AudioLocale() (LOCALE, error) {
|
func (s *Season) AudioLocale() (LOCALE, error) {
|
||||||
episodes, err := s.Episodes()
|
episodes, err := s.Episodes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -78,7 +80,7 @@ func (s *Season) AudioLocale() (LOCALE, error) {
|
||||||
return episodes[0].AudioLocale()
|
return episodes[0].AudioLocale()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Episodes returns all episodes which are available for the season
|
// Episodes returns all episodes which are available for the season.
|
||||||
func (s *Season) Episodes() (episodes []*Episode, err error) {
|
func (s *Season) Episodes() (episodes []*Episode, err error) {
|
||||||
if s.children != nil {
|
if s.children != nil {
|
||||||
return s.children, nil
|
return s.children, nil
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Stream contains information about all available video stream of an episode.
|
||||||
type Stream struct {
|
type Stream struct {
|
||||||
crunchy *Crunchyroll
|
crunchy *Crunchyroll
|
||||||
|
|
||||||
|
|
@ -22,7 +23,7 @@ type Stream struct {
|
||||||
streamURL string
|
streamURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamsFromID returns a stream by its api id
|
// StreamsFromID returns a stream by its api id.
|
||||||
func StreamsFromID(crunchy *Crunchyroll, id string) ([]*Stream, error) {
|
func StreamsFromID(crunchy *Crunchyroll, id string) ([]*Stream, error) {
|
||||||
return fromVideoStreams(crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
return fromVideoStreams(crunchy, fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/videos/%s/streams?locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||||
crunchy.Config.CountryCode,
|
crunchy.Config.CountryCode,
|
||||||
|
|
@ -35,7 +36,7 @@ func StreamsFromID(crunchy *Crunchyroll, id string) ([]*Stream, error) {
|
||||||
crunchy.Config.KeyPairID))
|
crunchy.Config.KeyPairID))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Formats returns all formats which are available for the stream
|
// Formats returns all formats which are available for the stream.
|
||||||
func (s *Stream) Formats() ([]*Format, error) {
|
func (s *Stream) Formats() ([]*Format, error) {
|
||||||
if s.children != nil {
|
if s.children != nil {
|
||||||
return s.children, nil
|
return s.children, nil
|
||||||
|
|
@ -70,7 +71,7 @@ func (s *Stream) Formats() ([]*Format, error) {
|
||||||
return formats, nil
|
return formats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fromVideoStreams returns all streams which are accessible via the endpoint
|
// fromVideoStreams returns all streams which are accessible via the endpoint.
|
||||||
func fromVideoStreams(crunchy *Crunchyroll, endpoint string) (streams []*Stream, err error) {
|
func fromVideoStreams(crunchy *Crunchyroll, endpoint string) (streams []*Stream, err error) {
|
||||||
resp, err := crunchy.request(endpoint)
|
resp, err := crunchy.request(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Subtitle contains the information about a video subtitle.
|
||||||
type Subtitle struct {
|
type Subtitle struct {
|
||||||
crunchy *Crunchyroll
|
crunchy *Crunchyroll
|
||||||
|
|
||||||
|
|
@ -13,6 +14,7 @@ type Subtitle struct {
|
||||||
Format string `json:"format"`
|
Format string `json:"format"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save writes the subtitle to the given io.Writer.
|
||||||
func (s Subtitle) Save(writer io.Writer) error {
|
func (s Subtitle) Save(writer io.Writer) error {
|
||||||
req, err := http.NewRequestWithContext(s.crunchy.Context, http.MethodGet, s.URL, nil)
|
req, err := http.NewRequestWithContext(s.crunchy.Context, http.MethodGet, s.URL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
4
url.go
4
url.go
|
|
@ -5,7 +5,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExtractEpisodesFromUrl extracts all episodes from an url.
|
// ExtractEpisodesFromUrl extracts all episodes from an url.
|
||||||
// If audio is not empty, the episodes gets filtered after the given locale
|
// If audio is not empty, the episodes gets filtered after the given locale.
|
||||||
func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Episode, error) {
|
func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Episode, error) {
|
||||||
series, episodes, err := c.ParseUrl(url)
|
series, episodes, err := c.ParseUrl(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -78,7 +78,7 @@ func (c *Crunchyroll) ExtractEpisodesFromUrl(url string, audio ...LOCALE) ([]*Ep
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseUrl parses the given url into a series or episode.
|
// ParseUrl parses the given url into a series or episode.
|
||||||
// The returning episode is a slice because non-beta urls have the same episode with different languages
|
// The returning episode is a slice because non-beta urls have the same episode with different languages.
|
||||||
func (c *Crunchyroll) ParseUrl(url string) (*Series, []*Episode, error) {
|
func (c *Crunchyroll) ParseUrl(url string) (*Series, []*Episode, error) {
|
||||||
if seriesId, ok := ParseBetaSeriesURL(url); ok {
|
if seriesId, ok := ParseBetaSeriesURL(url); ok {
|
||||||
series, err := SeriesFromID(c, seriesId)
|
series, err := SeriesFromID(c, seriesId)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"github.com/ByteDream/crunchyroll-go"
|
"github.com/ByteDream/crunchyroll-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AllLocales is an array of all available locales.
|
||||||
var AllLocales = []crunchyroll.LOCALE{
|
var AllLocales = []crunchyroll.LOCALE{
|
||||||
crunchyroll.JP,
|
crunchyroll.JP,
|
||||||
crunchyroll.US,
|
crunchyroll.US,
|
||||||
|
|
@ -18,7 +19,7 @@ var AllLocales = []crunchyroll.LOCALE{
|
||||||
crunchyroll.AR,
|
crunchyroll.AR,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateLocale validates if the given locale actually exist
|
// ValidateLocale validates if the given locale actually exist.
|
||||||
func ValidateLocale(locale crunchyroll.LOCALE) bool {
|
func ValidateLocale(locale crunchyroll.LOCALE) bool {
|
||||||
for _, l := range AllLocales {
|
for _, l := range AllLocales {
|
||||||
if l == locale {
|
if l == locale {
|
||||||
|
|
@ -28,7 +29,7 @@ func ValidateLocale(locale crunchyroll.LOCALE) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocaleLanguage returns the country by its locale
|
// LocaleLanguage returns the country by its locale.
|
||||||
func LocaleLanguage(locale crunchyroll.LOCALE) string {
|
func LocaleLanguage(locale crunchyroll.LOCALE) string {
|
||||||
switch locale {
|
switch locale {
|
||||||
case crunchyroll.JP:
|
case crunchyroll.JP:
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// SortEpisodesBySeason sorts the given episodes by their seasons.
|
// SortEpisodesBySeason sorts the given episodes by their seasons.
|
||||||
// Note that the same episodes just with different audio locales will cause problems
|
// Note that the same episodes just with different audio locales will cause problems.
|
||||||
func SortEpisodesBySeason(episodes []*crunchyroll.Episode) [][]*crunchyroll.Episode {
|
func SortEpisodesBySeason(episodes []*crunchyroll.Episode) [][]*crunchyroll.Episode {
|
||||||
sortMap := map[string]map[int][]*crunchyroll.Episode{}
|
sortMap := map[string]map[int][]*crunchyroll.Episode{}
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ func SortEpisodesBySeason(episodes []*crunchyroll.Episode) [][]*crunchyroll.Epis
|
||||||
return eps
|
return eps
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortEpisodesByAudio sort the given episodes by their audio locale
|
// SortEpisodesByAudio sort the given episodes by their audio locale.
|
||||||
func SortEpisodesByAudio(episodes []*crunchyroll.Episode) (map[crunchyroll.LOCALE][]*crunchyroll.Episode, error) {
|
func SortEpisodesByAudio(episodes []*crunchyroll.Episode) (map[crunchyroll.LOCALE][]*crunchyroll.Episode, error) {
|
||||||
eps := map[crunchyroll.LOCALE][]*crunchyroll.Episode{}
|
eps := map[crunchyroll.LOCALE][]*crunchyroll.Episode{}
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ func SortEpisodesByAudio(episodes []*crunchyroll.Episode) (map[crunchyroll.LOCAL
|
||||||
return eps, nil
|
return eps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MovieListingsByDuration sorts movie listings by their duration
|
// MovieListingsByDuration sorts movie listings by their duration.
|
||||||
type MovieListingsByDuration []*crunchyroll.MovieListing
|
type MovieListingsByDuration []*crunchyroll.MovieListing
|
||||||
|
|
||||||
func (mlbd MovieListingsByDuration) Len() int {
|
func (mlbd MovieListingsByDuration) Len() int {
|
||||||
|
|
@ -94,7 +94,7 @@ func (mlbd MovieListingsByDuration) Less(i, j int) bool {
|
||||||
return mlbd[i].DurationMS < mlbd[j].DurationMS
|
return mlbd[i].DurationMS < mlbd[j].DurationMS
|
||||||
}
|
}
|
||||||
|
|
||||||
// EpisodesByDuration sorts episodes by their duration
|
// EpisodesByDuration sorts episodes by their duration.
|
||||||
type EpisodesByDuration []*crunchyroll.Episode
|
type EpisodesByDuration []*crunchyroll.Episode
|
||||||
|
|
||||||
func (ebd EpisodesByDuration) Len() int {
|
func (ebd EpisodesByDuration) Len() int {
|
||||||
|
|
@ -107,6 +107,7 @@ func (ebd EpisodesByDuration) Less(i, j int) bool {
|
||||||
return ebd[i].DurationMS < ebd[j].DurationMS
|
return ebd[i].DurationMS < ebd[j].DurationMS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EpisodesByNumber sorts episodes after their episode number.
|
||||||
type EpisodesByNumber []*crunchyroll.Episode
|
type EpisodesByNumber []*crunchyroll.Episode
|
||||||
|
|
||||||
func (ebn EpisodesByNumber) Len() int {
|
func (ebn EpisodesByNumber) Len() int {
|
||||||
|
|
@ -119,7 +120,7 @@ func (ebn EpisodesByNumber) Less(i, j int) bool {
|
||||||
return ebn[i].EpisodeNumber < ebn[j].EpisodeNumber
|
return ebn[i].EpisodeNumber < ebn[j].EpisodeNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatsByResolution sorts formats after their resolution
|
// FormatsByResolution sorts formats after their resolution.
|
||||||
type FormatsByResolution []*crunchyroll.Format
|
type FormatsByResolution []*crunchyroll.Format
|
||||||
|
|
||||||
func (fbr FormatsByResolution) Len() int {
|
func (fbr FormatsByResolution) Len() int {
|
||||||
|
|
@ -140,6 +141,7 @@ func (fbr FormatsByResolution) Less(i, j int) bool {
|
||||||
return iResX+iResY < jResX+jResY
|
return iResX+iResY < jResX+jResY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubtitlesByLocale sorts subtitles after their locale.
|
||||||
type SubtitlesByLocale []*crunchyroll.Subtitle
|
type SubtitlesByLocale []*crunchyroll.Subtitle
|
||||||
|
|
||||||
func (sbl SubtitlesByLocale) Len() int {
|
func (sbl SubtitlesByLocale) Len() int {
|
||||||
|
|
|
||||||
15
video.go
15
video.go
|
|
@ -30,8 +30,10 @@ type video struct {
|
||||||
} `json:"images"`
|
} `json:"images"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Video is the base for Movie and Season.
|
||||||
type Video interface{}
|
type Video interface{}
|
||||||
|
|
||||||
|
// Movie contains information about a movie.
|
||||||
type Movie struct {
|
type Movie struct {
|
||||||
video
|
video
|
||||||
Video
|
Video
|
||||||
|
|
@ -40,7 +42,7 @@ type Movie struct {
|
||||||
|
|
||||||
children []*MovieListing
|
children []*MovieListing
|
||||||
|
|
||||||
// not generated when calling MovieFromID
|
// not generated when calling MovieFromID.
|
||||||
MovieListingMetadata struct {
|
MovieListingMetadata struct {
|
||||||
AvailabilityNotes string `json:"availability_notes"`
|
AvailabilityNotes string `json:"availability_notes"`
|
||||||
AvailableOffline bool `json:"available_offline"`
|
AvailableOffline bool `json:"available_offline"`
|
||||||
|
|
@ -65,7 +67,7 @@ type Movie struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MovieFromID returns a movie by its api id
|
// MovieFromID returns a movie by its api id.
|
||||||
func MovieFromID(crunchy *Crunchyroll, id string) (*Movie, error) {
|
func MovieFromID(crunchy *Crunchyroll, id string) (*Movie, error) {
|
||||||
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies/%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||||
crunchy.Config.CountryCode,
|
crunchy.Config.CountryCode,
|
||||||
|
|
@ -95,8 +97,6 @@ func MovieFromID(crunchy *Crunchyroll, id string) (*Movie, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MovieListing returns all videos corresponding with the movie.
|
// MovieListing returns all videos corresponding with the movie.
|
||||||
// Beside the normal movie, sometimes movie previews are returned too, but you can try to get the actual movie
|
|
||||||
// by sorting the returning MovieListing slice with the utils.MovieListingByDuration interface
|
|
||||||
func (m *Movie) MovieListing() (movieListings []*MovieListing, err error) {
|
func (m *Movie) MovieListing() (movieListings []*MovieListing, err error) {
|
||||||
if m.children != nil {
|
if m.children != nil {
|
||||||
return m.children, nil
|
return m.children, nil
|
||||||
|
|
@ -134,6 +134,7 @@ func (m *Movie) MovieListing() (movieListings []*MovieListing, err error) {
|
||||||
return movieListings, nil
|
return movieListings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Series contains information about an anime series.
|
||||||
type Series struct {
|
type Series struct {
|
||||||
video
|
video
|
||||||
Video
|
Video
|
||||||
|
|
@ -156,13 +157,13 @@ type Series struct {
|
||||||
MatureRatings []string `json:"mature_ratings"`
|
MatureRatings []string `json:"mature_ratings"`
|
||||||
SeasonCount int `json:"season_count"`
|
SeasonCount int `json:"season_count"`
|
||||||
|
|
||||||
// not generated when calling SeriesFromID
|
// not generated when calling SeriesFromID.
|
||||||
SearchMetadata struct {
|
SearchMetadata struct {
|
||||||
Score float64 `json:"score"`
|
Score float64 `json:"score"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SeriesFromID returns a series by its api id
|
// SeriesFromID returns a series by its api id.
|
||||||
func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) {
|
func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) {
|
||||||
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
resp, err := crunchy.request(fmt.Sprintf("https://beta-api.crunchyroll.com/cms/v2/%s/%s/%s/movies?movie_listing_id=%s&locale=%s&Signature=%s&Policy=%s&Key-Pair-Id=%s",
|
||||||
crunchy.Config.CountryCode,
|
crunchy.Config.CountryCode,
|
||||||
|
|
@ -191,7 +192,7 @@ func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error) {
|
||||||
return series, nil
|
return series, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seasons returns all seasons of a series
|
// Seasons returns all seasons of a series.
|
||||||
func (s *Series) Seasons() (seasons []*Season, err error) {
|
func (s *Series) Seasons() (seasons []*Season, err error) {
|
||||||
if s.children != nil {
|
if s.children != nil {
|
||||||
return s.children, nil
|
return s.children, nil
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue