Artist import, page
This commit is contained in:
@@ -12,4 +12,11 @@
|
||||
//
|
||||
//= require jquery
|
||||
//= require jquery_ujs
|
||||
//= require mustache
|
||||
//= require hogan
|
||||
//= require underscore
|
||||
//= require backbone
|
||||
//= require backbone_rails_sync
|
||||
//= require backbone_datalink
|
||||
//= require backbone/beat_haven
|
||||
//= require_tree .
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#= require_self
|
||||
#= require_tree ./templates
|
||||
#= require_tree ./models
|
||||
#= require_tree ./views
|
||||
#= require_tree ./routers
|
||||
|
||||
window.BeatHaven =
|
||||
Models: {}
|
||||
Collections: {}
|
||||
Routers: {}
|
||||
Views: {}
|
||||
|
||||
init: ->
|
||||
new BeatHaven.Routers.Artist()
|
||||
Backbone.history.start();
|
||||
|
||||
$ ->
|
||||
BeatHaven.init()
|
||||
@@ -0,0 +1,2 @@
|
||||
class BeatHaven.Models.Artist extends Backbone.Model
|
||||
urlRoot: "/api/artists"
|
||||
@@ -0,0 +1,9 @@
|
||||
class BeatHaven.Routers.Artist extends Backbone.Router
|
||||
routes:
|
||||
"artist/:name": "show"
|
||||
|
||||
show: (name) ->
|
||||
artist = new BeatHaven.Models.Artist(id: name)
|
||||
artist.fetch()
|
||||
view = new BeatHaven.Views.ArtistShow(model: artist)
|
||||
$("#main").html(view.render().el)
|
||||
@@ -0,0 +1,39 @@
|
||||
<div class="artist-page">
|
||||
<h1>{{name}}</h1>
|
||||
<div class="artist-info">
|
||||
<div class="pic">
|
||||
<img src="{{pic}}" alt="{{name}}">
|
||||
</div>
|
||||
<div class="bio">{{bio}}</div>
|
||||
</div>
|
||||
<div class="albums">
|
||||
{{#albums}}
|
||||
<div class="album">
|
||||
<div class="pic">
|
||||
<img src="{{pic_safe}}" alt="{{title}}"><br>
|
||||
<a href="" class="btn btn-success play-all"><i class="icon-plus icon-white"></i> Add to playlist</a>
|
||||
<a href="" class="btn btn-info play-all"><i class="icon-play icon-white"></i> Play</a>
|
||||
</div>
|
||||
<h2 class="title">{{title}} ({{year}})</h2>
|
||||
<div class="tracks">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
{{#tracks}}
|
||||
<tr>
|
||||
<td class="title">
|
||||
<a href="" class="btn btn-round track-play"><i class="icon-play"></i></a>
|
||||
<a href="" class="track-link">{{title}}</a>
|
||||
</td>
|
||||
<td class="length">
|
||||
<span class="length">{{length}}</span>
|
||||
<a href="" class="btn btn-round track-add"><i class="icon-plus"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{{/tracks}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{{/albums}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
class BeatHaven.Views.ArtistShow extends Backbone.View
|
||||
template: HoganTemplates["backbone/templates/artists/show"]
|
||||
|
||||
initialize: ->
|
||||
@model.on("change", @render, this)
|
||||
|
||||
render: ->
|
||||
return this if typeof @model.attributes.id is "string"
|
||||
$(@el).html(@template.render(@model.toJSON()))
|
||||
this
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||
* listed below.
|
||||
*
|
||||
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
||||
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
||||
*
|
||||
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
||||
* compiled file, but it's generally better to create a new file per style scope.
|
||||
*
|
||||
*= require_self
|
||||
*= require_tree .
|
||||
*/
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,132 @@
|
||||
.artist-page {
|
||||
h1 {
|
||||
font-family: Lobster, Georgia, serif;
|
||||
font-size: 42px;
|
||||
line-height: 64px;
|
||||
letter-spacing: 1px;
|
||||
text-shadow: 1px 1px 0 rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
.artist-info {
|
||||
min-height: 250px;
|
||||
margin-bottom: 50px;
|
||||
|
||||
.pic {
|
||||
float: left;
|
||||
|
||||
img {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
border-size: 1px;
|
||||
border-style: solid;
|
||||
border-color: rgba(255, 255, 255, .2);
|
||||
border-radius: 5px;
|
||||
box-shadow: 1px 1px 5px rgba(0, 0, 0, .2);
|
||||
}
|
||||
}
|
||||
.bio {
|
||||
margin-left: 270px;
|
||||
font-family: "Source Sans Pro", Helvetica, sans-serif;
|
||||
font-size: 22px;
|
||||
line-height: 26px;
|
||||
text-shadow: 1px 1px 0 rgba(0, 0, 0, .1);
|
||||
}
|
||||
}
|
||||
|
||||
.album {
|
||||
min-height: 250px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
h2 {
|
||||
font-family: Lobster, Georgia, Serif;
|
||||
font-size: 26px;
|
||||
text-shadow: 1px 1px 0 rgba(0, 0, 0, .2);
|
||||
}
|
||||
.pic {
|
||||
float: left;
|
||||
height: 300px;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
background-color: #a0a0a0;
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
border-size: 1px;
|
||||
border-style: solid;
|
||||
border-color: rgba(255, 255, 255, .2);
|
||||
border-radius: 5px;
|
||||
box-shadow: 1px 1px 5px rgba(0, 0, 0, .2);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
margin-left: 270px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.tracks {
|
||||
margin-left: 270px;
|
||||
|
||||
tr:hover {
|
||||
td {
|
||||
background-color: rgba(200, 200, 200, .3);
|
||||
|
||||
.track-play i {
|
||||
opacity: 1;
|
||||
}
|
||||
&.length {
|
||||
.track-add {
|
||||
display: block;
|
||||
}
|
||||
.length {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
position: relative;
|
||||
font-family: "Source Sans Pro", Helvetica, sans-serif;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
text-shadow: 1px 1px 0 rgba(0, 0, 0, .1);
|
||||
|
||||
.btn-round {
|
||||
width: 10px;
|
||||
height: 22px;
|
||||
border-radius: 50px;
|
||||
|
||||
i {
|
||||
margin: 3px 0 0 -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td.title {
|
||||
padding-left: 40px;
|
||||
.track-play {
|
||||
position: absolute;
|
||||
margin: -2px 0 0 -40px;
|
||||
|
||||
i {
|
||||
opacity: .3;
|
||||
}
|
||||
}
|
||||
.track-link {
|
||||
color: #303030;
|
||||
}
|
||||
}
|
||||
|
||||
td.length {
|
||||
text-align: right;
|
||||
|
||||
.track-add {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
margin: -2px 0 0 0;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
module Api
|
||||
class AlbumsController < ::ApplicationController
|
||||
def picture
|
||||
album = Album.find(params[:id])
|
||||
redirect_to album.load_pic
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
module Api
|
||||
class ArtistsController < ::ApplicationController
|
||||
def show
|
||||
artist = Artist.with_name(params[:id].gsub("+", " "))
|
||||
return render json: { fail: true } if artist.nil?
|
||||
|
||||
render json: artist.dump_json
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,7 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
protect_from_forgery
|
||||
|
||||
def main
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
class Album < ActiveRecord::Base
|
||||
belongs_to :artist
|
||||
has_many :tracks
|
||||
|
||||
attr_accessible :artist_id, :pic, :rovi_id, :title, :year
|
||||
|
||||
def pic_safe
|
||||
unless pic.nil?
|
||||
pic
|
||||
else
|
||||
"/api/albums/#{id}/picture"
|
||||
end
|
||||
end
|
||||
|
||||
def load_pic
|
||||
info = BeatParser::Sources::Lastfm.album_info(artist.name, title)
|
||||
unless info[:pic].nil?
|
||||
update_attributes(pic: info[:pic])
|
||||
info[:pic]
|
||||
else
|
||||
"/assets/images/album-dummy.png"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,88 @@
|
||||
class Artist < ActiveRecord::Base
|
||||
has_many :albums
|
||||
has_many :performers
|
||||
has_many :tracks, through: :performers
|
||||
has_many :genres, through: :artist_genres
|
||||
|
||||
attr_accessible :bio, :is_group, :name, :pic, :rovi_id
|
||||
|
||||
def loaded?
|
||||
pic? && bio?
|
||||
end
|
||||
|
||||
def dump_json
|
||||
serialized = to_json(
|
||||
include: {
|
||||
albums: {
|
||||
include: {
|
||||
tracks: {
|
||||
methods: [:length],
|
||||
except: [:created_at, :updated_at, :rovi_id, :album_id]
|
||||
}
|
||||
},
|
||||
except: [:created_at, :updated_at, :rovi_id, :pic],
|
||||
methods: [:pic_safe]
|
||||
}
|
||||
},
|
||||
except: [:created_at, :updated_at, :rovi_id],
|
||||
)
|
||||
end
|
||||
|
||||
class << self
|
||||
def with_name(name)
|
||||
# DB lookup
|
||||
artist = find_by_name(name)
|
||||
return artist unless artist.nil?
|
||||
|
||||
# Rovi correction
|
||||
rovi_artist = Robbie::Artist.find_by_name(name)
|
||||
return artist if rovi_artist && artist = find_by_rovi_id(rovi_artist.id)
|
||||
|
||||
# Parsing artist if ok
|
||||
import(rovi_artist) if rovi_artist
|
||||
end
|
||||
|
||||
def import(rovi_artist)
|
||||
data = BeatParser::Aggregator.new.combine(rovi_artist.id)
|
||||
artist = Artist.find_or_create_by_rovi_id(data[:id])
|
||||
artist.update_attributes(
|
||||
name: data[:name],
|
||||
is_group: data[:is_group],
|
||||
pic: data[:pic],
|
||||
bio: data[:bio]
|
||||
)
|
||||
data[:albums].each do |album_meta|
|
||||
album = Album.find_or_create_by_rovi_id(album_meta[:id])
|
||||
album.update_attributes(
|
||||
artist_id: artist.id,
|
||||
title: album_meta[:title],
|
||||
year: album_meta[:year].to_i
|
||||
)
|
||||
album_meta[:tracks].each do |track_meta|
|
||||
track = Track.find_or_create_by_rovi_id(track_meta[:id])
|
||||
track.update_attributes(
|
||||
album_id: album.id,
|
||||
disc_id: track_meta[:disc_id],
|
||||
position: track_meta[:position],
|
||||
title: track_meta[:title],
|
||||
duration: track_meta[:duration]
|
||||
)
|
||||
track_meta[:artists].each do |performer|
|
||||
performer_artist = Artist.find_or_create_by_rovi_id(performer[:id])
|
||||
performer_artist.update_attributes(
|
||||
name: performer[:name]
|
||||
)
|
||||
Performer.find_or_create_by_artist_id_and_track_id(performer_artist.id, track.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
data[:genres].each do |genre_meta|
|
||||
genre = Genre.find_or_create_by_rovi_id(genre_meta[:id])
|
||||
genre.update_attributes(
|
||||
name: genre_meta[:name]
|
||||
)
|
||||
ArtistGenre.find_or_create_by_artist_id_and_genre_id(artist.id, genre.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
class ArtistGenre < ActiveRecord::Base
|
||||
attr_accessible :artist_id, :genre_id
|
||||
end
|
||||
@@ -0,0 +1,3 @@
|
||||
class Genre < ActiveRecord::Base
|
||||
attr_accessible :name, :rovi_id
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
class Performer < ActiveRecord::Base
|
||||
belongs_to :artist
|
||||
belongs_to :track
|
||||
|
||||
attr_accessible :artist_id, :track_id
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
class Track < ActiveRecord::Base
|
||||
belongs_to :album
|
||||
has_many :performers
|
||||
has_many :artists, through: :performers
|
||||
|
||||
attr_accessible :album_id, :disc_id, :duration, :position, :rovi_id, :title
|
||||
|
||||
def length
|
||||
return if duration.nil?
|
||||
length = duration.divmod(60).map(&:to_s)
|
||||
length[1] = "0" << length[1] if length[1].length == 1
|
||||
length.join(":")
|
||||
end
|
||||
end
|
||||
@@ -2,13 +2,20 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>BeatHaven</title>
|
||||
<%= stylesheet_link_tag "application", :media => "all" %>
|
||||
<%= stylesheet_link_tag "application", media: "all" %>
|
||||
<%= javascript_include_tag "application" %>
|
||||
<%= csrf_meta_tags %>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<%= yield %>
|
||||
<div class="navbar navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<a class="brand" href="">BeatHaven</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" id="main">Loading...</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user