Apple TV for Music

Apple TV for Music

I wanted to play my music through my Apple TV. Apple have a service for that they think I should use instead.

DAAP

iTunes music sharing uses the Digital Audio Access Protocol (DAAP). To summarise, DAAP is DLNA for Apple. I don't really like either. A DAAP server is really just a HTTP server that returns XML. iTunes discovers local (exclusively local) DAAP servers using zeroconf. My hope was that my Apple TV would be able to play my music library from my NAS, if my NAS could run a DAAP server.

ALAC and FLAC

In my case, all my music is FLAC. So it needs transcoding. To do that, I rigged a quick-and-dirty script in ruby:

#!/usr/bin/env ruby
require 'shellwords'
require 'pry'
WORKER_COUNT=10
FLAC_ROOT="/mnt/H2C/music/flac"
ALAC_ROOT="/mnt/H2C/music/alac"

TranscoderOpts = Struct.new(:codec, :container, :bitrate)

class Album
  attr_accessor :src_root, :dst_root, :genre, :album
  attr_accessor :in_files, :out_files, :src_art, :dst_art

  def initialize src_root, dst_root, genre, album
    @src_root = src_root
    @dst_root = dst_root
    @genre = genre
    @album = album
    @files = Dir.open("#{src_root}/#{genre}/#{album}").children.select { |f| f.end_with? ".flac" }
    @in_files = @files.map { |in_file| "#{src_root}/#{genre}/#{album}/#{in_file}" }
    @out_files = @in_files.map { |in_file| in_file.gsub(".flac", ".m4a").gsub("/flac/", "/alac/") }
    @src_art = "#{src_root}/#{genre}/#{album}/folder.jpg"
    @dst_art = "#{dst_root}/#{genre}/#{album}/folder.jpg"
  end

  def flac_to_alac opts, queue
    puts "Creating #{@dst_root}/#{@genre}/#{@album}"
    `mkdir -p "#{@dst_root}/#{@genre}/#{@album}"`

    unless File.exist? @dst_art
      puts "Copying #{@src_art}"
      `cp "#{@src_art}" "#{@dst_art}"`
    end

    @in_files.each_with_index do |src, i|
      next if File.exist? @out_files[i]
      src = Shellwords.escape(src)
      dst = Shellwords.escape(@out_files[i])
      queue.push "ffmpeg -i #{src} -acodec #{opts.codec} -map a #{dst}"
    end
    puts "Appended  #{@in_files.count} tracks to queue for #{@genre}/#{@album}"
  end

  def flac_to_aac opts, queue
    unless Dir.exist? "#{@dst_root}/#{@genre}/#{@album}"
      puts "Creating #{@dst_root}/#{@genre}/#{@album}"
      `mkdir -p "#{@dst_root}/#{@genre}/#{@album}"`
    end

    unless File.exist? @dst_art
      puts "Copying #{@src_art}"
      `cp "#{@src_art}" "#{@dst_art}"`
    end

    @in_files.each_with_index do |src, i|
      next if File.exist? @out_files[i]
      src = Shellwords.escape(src)
      dst = Shellwords.escape(@out_files[i])
      queue.push "afconvert -d #{opts.codec} -f #{opts.container} -b #{opts.bitrate} #{src} -o #{dst}"
    end

    puts "Appended  #{@in_files.count} tracks to queue for #{@genre}/#{@album}"
  end
end

class Library
  attr_accessor :queue, :genres, :albums
  def initialize src, dst
    @src_root = src
    @dst_root = dst
    @conversion_queue = Queue.new
    @genres = Dir.open(src).children
    @albums = []
    @genres.each do |genre|
      genre_albums = Dir.open(@src_root + "/" + genre).children
      genre_albums.select! { |a| not a.start_with? "." }
      genre_albums.each { |album| @albums.push Album.new(@src_root, @dst_root, genre, album) }
    end
  end

  def convert_all opts
    case opts.codec
    when "alac" then @albums.each { |album| album.flac_to_alac(opts, @conversion_queue) }
    when "aac"  then @albums.each { |album| album.flac_to_aac(opts, @conversion_queue) }
    else exit 1
    end
    workers = []
    WORKER_COUNT.times do |i|
      workers.push Thread.new do
        puts "Starting worker #{i}"
        until @conversion_queue.empty?
          command = @conversion_queue.pop(true) rescue nil
          if command then `#{command}` end
        end
      end
    end

    workers.each { |worker| worker.join }
    puts "Complete"
  end
end

library = Library.new(FLAC_ROOT, ALAC_ROOT)
library.convert_all TranscoderOpts.new("alac", "m4af", nil)

Owntone

So, with my now-Apple-certified library, I needed a DAAP server. Owntone is such a DAAP server. Highly portable (it's just C), and a doddle to set up. In my case I built it from source with the web interface disabled:

./configure --disable-spotify --disable-webinterface --without-libwebsockets

After which, I configured it to look at my new ALAC library:

directories = { "/mnt/H2C/music/alac" }

Disappointment

Of course, it doesn't work. Owntone works. The files are fine. I can play from Owntone on a Mac without issue. But the geniuses at Apple decided that if you want to stream music from an iTunes Library (well, yes, that's what Owntone emulates) to tvOS - you should buy an Apple Music subscription.

Oh do sod off.

I suppose I'll end up writing an article about knocking up a Jellyfin client for tvOS at some point. I don't know swift, nor have any particular desire to know it. But I really do just want a basic genre/album/track selection. With a bit of album art taken from folder.jpg. Jellyfin is already running on my network, so I might as well use that API. We'll see.