crunchyroll

package module
v1.2.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 2, 2021 License: LGPL-3.0 Imports: 22 Imported by: 0

README

crunchyroll-go

A Go library & cli for the undocumented crunchyroll api.

You surely need a crunchyroll premium account to get full (api) access.

Code size License Go version Release

CLI 🖥️Library 📚Credits 🙏Notice 🗒️License ⚖

🖥️ CLI

✨ Features
  • Download single videos and entire series from crunchyroll
Get the executable
  • 📥 Download the latest binaries here or get it from below
  • If you use Arch btw. or any other Linux distro which is based on Arch Linux, you can download the package via the AUR:
    $ yay -S crunchyroll-go
    
  • 🛠 Build it yourself
    • use make (requires go to be installed):
    $ git clone https://github.com/ByteDream/crunchyroll-go
    $ cd crunchyroll-go
    $ make && sudo make install
    
    • use go:
    $ git clone https://github.com/ByteDream/crunchyroll-go
    $ cd crunchyroll-go/cmd/crunchyroll-go
    $ go build -o crunchy
    
📝 Examples
Login

Before you can do something, you have to login first.

This can be performed via crunchyroll account email and password.

$ crunchy login user@example.com password

or via session id

$ crunchy login --session-id 8e9gs135defhga790dvrf2i0eris8gts
Download

With the cli you can download single videos or entire series.

By default the cli tries to download the episode with your system language as audio. If no streams with your system language are available, the video will be downloaded with japanese audio and hardsubbed subtitles in your system language. If your system language is not supported, an error message will be displayed and en-US (american english) will be chosen as language.

$ crunchy download https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575

With -r best the video(s) will have the best available resolution (mostly 1920x1080 / Full HD).

$ crunchy download -r best https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575

The file is by default saved as a .ts (mpeg transport stream) file. .ts files may can't be played or are looking very weird (it depends on the video player you are using). With the -o flag, you can change the name (and file ending) of the output file. So if you want to save it as, for example, mp4 file, just name it whatever.mp4. You need ffmpeg to store the video in other file formats.

$ crunchy download -o "daaaaaaaaaaaaaaaarling.ts" https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575

With the --audio flag you can specify which audio the video should have and with --subtitle which subtitle it should have. Type crunchy help download to see all available locales.

$ crunchy download --audio ja-JP --subtitle de-DE https://www.crunchyroll.com/darling-in-the-franxx
Flags
  • --audio » forces audio of the video(s)

  • --subtitle » forces subtitle of the video(s)

  • --no-hardsub » forces that the subtitles are stored as a separate file and are not directly embedded into the video

  • --only-sub » downloads only the subtitles without the corresponding video

  • -d, --directory » directory to download the video(s) to

  • -o, --output » name of the output file

  • -r, --resolution » the resolution of the video(s). best for best resolution, worst for worst

  • --alternative-progress » shows an alternative, not so user-friendly progress instead of the progress bar

  • -g, --goroutines » sets how many parallel segment downloads should be used

Help
  • General help
    $ crunchy help
    
  • Login help
    $ crunchy help login
    
  • Download help
    $ crunchy help download
    
Global flags

These flags you can use across every sub-command

  • -q, --quiet » disables all output

  • -v, --verbose » shows additional debug output

  • --color » adds color to the output (works only on not windows systems)

  • -p, --proxy » use a proxy to hide your ip / redirect your traffic

  • -l, --locale » the language to display video specific things like the title. default is your system language

📚 Library

Download the library via go get

$ go get github.com/ByteDream/crunchyroll-go
📝 Examples
func main() {
    // login with credentials 
    crunchy, err := crunchyroll.LoginWithCredentials("user@example.com", "password", crunchyroll.US, http.DefaultClient)
    if err != nil {
        panic(err)
    }

    // finds a series or movie by a crunchyroll link
    video, err := crunchy.FindVideo("https://www.crunchyroll.com/darling-in-the-franxx")
    if err != nil {
        panic(err)
    }

    series := video.(*crunchyroll.Series)
    seasons, err := series.Seasons()
    if err != nil {
        panic(err)
    }
    fmt.Printf("Found %d seasons for series %s\n", len(seasons), series.Title)

    // search `Darling` and return 20 results
    series, movies, err := crunchy.Search("Darling", 20)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Found %d series and %d movies for query `Darling`\n", len(series), len(movies))
}
func main() {
    crunchy, err := crunchyroll.LoginWithSessionID("8e9gs135defhga790dvrf2i0eris8gts", crunchyroll.US, http.DefaultClient)
    if err != nil {
        panic(err)
    }

    // returns an episode slice with all episodes which are matching the given url.
    // the episodes in the returning slice differs from the underlying streams, but are all pointing to the first ditf episode
    episodes, err := crunchy.FindEpisode("https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Found %d episodes\n", len(episodes))
}

Structure

Because of the apis structure, it can lead very fast much redundant code for simple tasks, like getting all episodes with japanese audio and german subtitle. For this case and some other, the api has a utility called Structure in its utils.

func main() {
    crunchy, err := crunchyroll.LoginWithCredentials("user@example.com", "password", crunchyroll.US, http.DefaultClient)
    if err != nil {
        panic(err)
    }

    // search `Darling` and return 20 results (series and movies) or less
    series, movies, err := crunchy.Search("Darling", 20)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Found %d series and %d movies for search query `Darling`\n", len(series), len(movies))

    seasons, err := series[0].Seasons()
    if err != nil {
        panic(err)
    }

    // in the crunchyroll.utils package, you find some structs which can be used to simplify tasks.
    // you can recursively search all underlying content
    seriesStructure := utils.NewSeasonStructure(seasons)

    // this returns every format of all the above given seasons
    formats, err := seriesStructure.Formats()
    if err != nil {
        panic(err)
    }
    fmt.Printf("Found %d formats\n", len(formats))

    filteredFormats, err := seriesStructure.FilterFormatsByLocales(crunchyroll.JP, crunchyroll.DE, true)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Found %d formats with japanese audio and hardsubbed german subtitles\n", len(filteredFormats))

    // reverse sorts the formats after their resolution by calling a sort type which is also defined in the api utils
    // and stores the format with the highest resolution in a variable
    sort.Sort(sort.Reverse(utils.FormatsByResolution(filteredFormats)))
    format := formats[0]
    // get the episode from which the format is a child
    episode, err := seriesStructure.FilterEpisodeByFormat(format)
    if err != nil {
        panic(err)
    }

    file, err := os.Create(fmt.Sprintf("%s.ts", episode.Title))
    if err != nil {
        panic(err)
    }

    // download the format to the file
    if err := format.DownloadGoroutines(file, 4, nil); err != nil {
        panic(err)
    }
    fmt.Printf("Downloaded %s with %s resolution and %.2f fps as %s\n", episode.Title, format.Video.Resolution, format.Video.FPS, file.Name())

    // for more useful structure function just let your IDE's autocomplete make its thing
}

As you can see in the example above, most of the crunchyroll.utils Structure functions are returning errors. There is a build-in functionality with are avoiding causing the most errors and let you safely ignore them as well. Note that errors still can appear

func main() {
    crunchy, err := crunchyroll.LoginWithCredentials("user@example.com", "password", crunchyroll.US, http.DefaultClient)
    if err != nil {
        panic(err)
    }

    foundEpisodes, err := crunchy.FindEpisode("https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575")
    if err != nil {
    	panic(err)
    }
    episodeStructure := utils.NewEpisodeStructure(foundEpisodes)

    // this function recursively calls all api endpoints, receives everything and stores it in memory,
    // so that after executing this, no more request to the crunchyroll server has to be made.
    // note that it could cause much network load while running this method.
    //
    // you should check the InitAllState before, because InitAll could have been already called or
    // another function has the initialization as side effect and re-initializing everything
    // will change every pointer in the struct which can cause massive problems afterwards. 
    if !episodeStructure.InitAllState() {
        if err := episodeStructure.InitAll(); err != nil {
            panic(err)
        }
    }

    formats, _ := episodeStructure.Formats()
    streams, _ := episodeStructure.Streams()
    episodes, _ := episodeStructure.Episodes()
    fmt.Printf("Initialized %d formats, %d streams and %d episodes\n", len(formats), len(streams), len(episodes))
}
Tests

You can also run test to see if the api works correctly. Before doing this, make sure to either set your crunchyroll email and password or sessions as environment variable. The email variable has to be named EMAIL and the password variable PASSWORD. If you want to use your session id, the variable must be named SESSION_ID.

You can run the test via make

$ make test

or via go directly

$ go test .

🙏 Credits

Kamyroll-Python
  • Extracted all api endpoints and the login process from this
m3u8
  • Decrypting mpeg stream files
All libraries
  • m3u8 (not the m3u8 library from above) » mpeg stream info library
  • cobra » cli library

🗒️ Notice

I would really appreciate if someone rewrites the complete cli. I'm not satisfied with it's current structure but at the moment I have no time and no desire to do it myself.

⚖ License

This project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0) - see the LICENSE file for more details.

Documentation

Index

Constants

View Source
const (
	JP LOCALE = "ja-JP"
	US        = "en-US"
	LA        = "es-LA"
	ES        = "es-ES"
	FR        = "fr-FR"
	BR        = "pt-BR"
	IT        = "it-IT"
	DE        = "de-DE"
	RU        = "ru-RU"
	ME        = "ar-ME"
)

Variables

This section is empty.

Functions

func MatchEpisode deprecated added in v1.1.0

func MatchEpisode(url string) (seriesName, title string, ok bool)

MatchEpisode tries to extract the crunchyroll series name and title out of the given url

Deprecated: Use ParseEpisodeURL instead

func MatchVideo added in v1.1.0

func MatchVideo(url string) (seriesName string, ok bool)

MatchVideo tries to extract the crunchyroll series / movie name out of the given url

func ParseBetaEpisodeURL added in v1.2.2

func ParseBetaEpisodeURL(url string) (episodeId string, ok bool)

ParseBetaEpisodeURL tries to extract the episode id of the given crunchyroll beta url, pointing to an episode

func ParseBetaSeriesURL added in v1.2.2

func ParseBetaSeriesURL(url string) (seasonId string, ok bool)

ParseBetaSeriesURL tries to extract the season id of the given crunchyroll beta url, pointing to a season

func ParseEpisodeURL added in v1.2.1

func ParseEpisodeURL(url string) (seriesName, title string, episodeNumber int, webId int, ok bool)

ParseEpisodeURL tries to extract the crunchyroll series name, title, episode number and web id out of the given crunchyroll url Note that the episode number can be misleading. For example if an episode has the episode number 23.5 (slime isekai) the episode number will be 235

Types

type AccessError

type AccessError struct {
	URL     string
	Body    []byte
	Message string
	// contains filtered or unexported fields
}

AccessError is an error which will be returned when some special sort of api request fails. See Crunchyroll.request when the error gets used

func (*AccessError) Error

func (ae *AccessError) Error() string

type Crunchyroll

type Crunchyroll struct {
	// Client is the http.Client to perform all requests over
	Client *http.Client
	// Locale specifies in which language all results should be returned / requested
	Locale LOCALE
	// SessionID is the crunchyroll session id which was used for authentication
	SessionID string

	// Config stores parameters which are needed by some api calls
	Config struct {
		TokenType   string
		AccessToken string

		CountryCode    string
		Premium        bool
		Channel        string
		Policy         string
		Signature      string
		KeyPairID      string
		AccountID      string
		ExternalID     string
		MaturityRating string
	}
}

func LoginWithCredentials

func LoginWithCredentials(email string, password string, locale LOCALE, client *http.Client) (*Crunchyroll, error)

LoginWithCredentials logs in via crunchyroll email and password

func LoginWithSessionID

func LoginWithSessionID(sessionID string, locale LOCALE, client *http.Client) (*Crunchyroll, error)

LoginWithSessionID logs in via a crunchyroll session id. Session ids are automatically generated as a cookie when visiting https://www.crunchyroll.com

func (*Crunchyroll) FindEpisode

func (c *Crunchyroll) FindEpisode(url string) ([]*Episode, error)

FindEpisode finds an episode by its crunchyroll link e.g. https://www.crunchyroll.com/darling-in-the-franxx/episode-1-alone-and-lonesome-759575

func (*Crunchyroll) FindVideo

func (c *Crunchyroll) FindVideo(seriesUrl string) (Video, error)

FindVideo finds a Video (Season or Movie) by a crunchyroll link e.g. https://www.crunchyroll.com/darling-in-the-franxx

func (*Crunchyroll) Search

func (c *Crunchyroll) Search(query string, limit uint) (s []*Series, m []*Movie, err error)

Search searches a query and returns all found series and movies within the given limit

type Episode

type Episode struct {
	ID           string `json:"id"`
	SeriesID     string `json:"series_id"`
	SeriesTitle  string `json:"series_title"`
	SeasonNumber int    `json:"season_number"`

	Episode             string  `json:"episode"`
	EpisodeNumber       int     `json:"episode_number"`
	SequenceNumber      float64 `json:"sequence_number"`
	ProductionEpisodeID string  `json:"production_episode_id"`

	Title            string `json:"title"`
	SlugTitle        string `json:"slug_title"`
	Description      string `json:"description"`
	NextEpisodeID    string `json:"next_episode_id"`
	NextEpisodeTitle string `json:"next_episode_title"`

	HDFlag        bool `json:"hd_flag"`
	IsMature      bool `json:"is_mature"`
	MatureBlocked bool `json:"mature_blocked"`

	EpisodeAirDate time.Time `json:"episode_air_date"`

	IsSubbed       bool     `json:"is_subbed"`
	IsDubbed       bool     `json:"is_dubbed"`
	IsClip         bool     `json:"is_clip"`
	SeoTitle       string   `json:"seo_title"`
	SeoDescription string   `json:"seo_description"`
	SeasonTags     []string `json:"season_tags"`

	AvailableOffline bool   `json:"available_offline"`
	Slug             string `json:"slug"`

	Images struct {
		Thumbnail [][]struct {
			Width  int    `json:"width"`
			Height int    `json:"height"`
			Type   string `json:"type"`
			Source string `json:"source"`
		} `json:"thumbnail"`
	} `json:"images"`

	DurationMS    int    `json:"duration_ms"`
	IsPremiumOnly bool   `json:"is_premium_only"`
	ListingID     string `json:"listing_id"`

	SubtitleLocales []LOCALE `json:"subtitle_locales"`
	Playback        string   `json:"playback"`

	AvailabilityNotes string `json:"availability_notes"`

	StreamID string
	// contains filtered or unexported fields
}

func EpisodeFromID

func EpisodeFromID(crunchy *Crunchyroll, id string) (*Episode, error)

EpisodeFromID returns an episode by its api id

func (*Episode) AudioLocale

func (e *Episode) AudioLocale() (LOCALE, error)

AudioLocale returns the audio locale of the episode. 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. Otherwise, if you call this function on every episode it will cause a massive delay and redundant network overload since it calls an api endpoint every time

func (*Episode) Streams

func (e *Episode) Streams() ([]*Stream, error)

Streams returns all streams which are available for the episode

type Format

type Format struct {
	ID string
	// FormatType represents if the format parent is an episode or a movie
	FormatType  FormatType
	Video       *m3u8.Variant
	AudioLocale LOCALE
	Hardsub     LOCALE
	Subtitles   []*Subtitle
	// contains filtered or unexported fields
}

func (*Format) Download deprecated

func (f *Format) Download(output *os.File, onSegmentDownload func(segment *m3u8.MediaSegment, current, total int, file *os.File, err error) error) error

Download calls DownloadGoroutines with 4 goroutines. See DownloadGoroutines for more details

Deprecated: Use DownloadGoroutines instead

func (*Format) DownloadGoroutines added in v1.2.0

func (f *Format) DownloadGoroutines(output *os.File, goroutines int, onSegmentDownload func(segment *m3u8.MediaSegment, current, total int, file *os.File) error) error

DownloadGoroutines downloads the format to the given output file (as .ts file). See Format.DownloadSegments for more information

func (*Format) DownloadSegments

func (f *Format) DownloadSegments(outputDir string, goroutines int, onSegmentDownload func(segment *m3u8.MediaSegment, current, total int, file *os.File) error) error

DownloadSegments downloads every mpeg transport stream segment to a given directory (more information below). After every segment download onSegmentDownload will be called with:

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 filename is always <number of downloaded segment>.ts

Short explanation:

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.
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

type FormatType

type FormatType string
const (
	EPISODE FormatType = "episodes"
	MOVIE              = "movies"
)

type LOCALE

type LOCALE string

LOCALE represents a locale / language

type Movie

type Movie struct {
	Video

	// not generated when calling MovieFromID
	MovieListingMetadata struct {
		AvailabilityNotes   string   `json:"availability_notes"`
		AvailableOffline    bool     `json:"available_offline"`
		DurationMS          int      `json:"duration_ms"`
		ExtendedDescription string   `json:"extended_description"`
		FirstMovieID        string   `json:"first_movie_id"`
		IsDubbed            bool     `json:"is_dubbed"`
		IsMature            bool     `json:"is_mature"`
		IsPremiumOnly       bool     `json:"is_premium_only"`
		IsSubbed            bool     `json:"is_subbed"`
		MatureRatings       []string `json:"mature_ratings"`
		MovieReleaseYear    int      `json:"movie_release_year"`
		SubtitleLocales     []LOCALE `json:"subtitle_locales"`
	} `json:"movie_listing_metadata"`

	Playback string `json:"playback"`

	PromoDescription string `json:"promo_description"`
	PromoTitle       string `json:"promo_title"`
	SearchMetadata   struct {
		Score float64 `json:"score"`
	}
	// contains filtered or unexported fields
}

func MovieFromID

func MovieFromID(crunchy *Crunchyroll, id string) (*Movie, error)

MovieFromID returns a movie by its api id

func (*Movie) MovieListing

func (m *Movie) MovieListing() (movieListings []*MovieListing, err error)

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

type MovieListing

type MovieListing struct {
	ID string `json:"id"`

	Title       string `json:"title"`
	Slug        string `json:"slug"`
	SlugTitle   string `json:"slug_title"`
	Description string `json:"description"`

	Images struct {
		Thumbnail [][]struct {
			Width  int    `json:"width"`
			Height int    `json:"height"`
			Type   string `json:"type"`
			Source string `json:"source"`
		} `json:"thumbnail"`
	} `json:"images"`

	DurationMS       int    `json:"duration_ms"`
	IsPremiumOnly    bool   `json:"is_premium_only"`
	ListeningID      string `json:"listening_id"`
	IsMature         bool   `json:"is_mature"`
	AvailableOffline bool   `json:"available_offline"`
	IsSubbed         bool   `json:"is_subbed"`
	IsDubbed         bool   `json:"is_dubbed"`

	Playback          string `json:"playback"`
	AvailabilityNotes string `json:"availability_notes"`
	// contains filtered or unexported fields
}

func MovieListingFromID

func MovieListingFromID(crunchy *Crunchyroll, id string) (*MovieListing, error)

MovieListingFromID returns a movie listing by its api id

func (*MovieListing) AudioLocale

func (ml *MovieListing) AudioLocale() (LOCALE, error)

AudioLocale is same as Episode.AudioLocale

func (*MovieListing) Streams

func (ml *MovieListing) Streams() ([]*Stream, error)

Streams returns all streams which are available for the movie listing

type Season

type Season struct {
	ID             string   `json:"id"`
	Title          string   `json:"title"`
	SlugTitle      string   `json:"slug_title"`
	SeriesID       string   `json:"series_id"`
	SeasonNumber   int      `json:"season_number"`
	IsComplete     bool     `json:"is_complete"`
	Description    string   `json:"description"`
	Keywords       []string `json:"keywords"`
	SeasonTags     []string `json:"season_tags"`
	IsMature       bool     `json:"is_mature"`
	MatureBlocked  bool     `json:"mature_blocked"`
	IsSubbed       bool     `json:"is_subbed"`
	IsDubbed       bool     `json:"is_dubbed"`
	IsSimulcast    bool     `json:"is_simulcast"`
	SeoTitle       string   `json:"seo_title"`
	SeoDescription string   `json:"seo_description"`

	Language LOCALE
	// contains filtered or unexported fields
}

func SeasonFromID

func SeasonFromID(crunchy *Crunchyroll, id string) (*Season, error)

SeasonFromID returns a season by its api id

func (*Season) Episodes

func (s *Season) Episodes() (episodes []*Episode, err error)

Episodes returns all episodes which are available for the season

type Series

type Series struct {
	Video

	PromoDescription string `json:"promo_description"`
	PromoTitle       string `json:"promo_title"`

	AvailabilityNotes   string   `json:"availability_notes"`
	EpisodeCount        int      `json:"episode_count"`
	ExtendedDescription string   `json:"extended_description"`
	IsDubbed            bool     `json:"is_dubbed"`
	IsMature            bool     `json:"is_mature"`
	IsSimulcast         bool     `json:"is_simulcast"`
	IsSubbed            bool     `json:"is_subbed"`
	MatureBlocked       bool     `json:"mature_blocked"`
	MatureRatings       []string `json:"mature_ratings"`
	SeasonCount         int      `json:"season_count"`

	// not generated when calling SeriesFromID
	SearchMetadata struct {
		Score float64 `json:"score"`
	}
	// contains filtered or unexported fields
}

func SeriesFromID

func SeriesFromID(crunchy *Crunchyroll, id string) (*Series, error)

SeriesFromID returns a series by its api id

func (*Series) Seasons

func (s *Series) Seasons() (seasons []*Season, err error)

Seasons returns all seasons of a series

type Stream

type Stream struct {
	HardsubLocale LOCALE
	AudioLocale   LOCALE
	Subtitles     []*Subtitle
	// contains filtered or unexported fields
}

func StreamsFromID

func StreamsFromID(crunchy *Crunchyroll, id string) ([]*Stream, error)

StreamsFromID returns a stream by its api id

func (*Stream) Formats

func (s *Stream) Formats() ([]*Format, error)

Formats returns all formats which are available for the stream

type Subtitle

type Subtitle struct {
	Locale LOCALE `json:"locale"`
	URL    string `json:"url"`
	Format string `json:"format"`
	// contains filtered or unexported fields
}

func (Subtitle) Download

func (s Subtitle) Download(file *os.File) error

type Video

type Video interface{}

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL