Python Script To Copy a Playlist and Linked Files

Most tools for copying files onto MP3 players (often actually a phone these days) work on the basis that you copy your entire music catalogue and then make playlists linking to various files on it.

It can be a trickier process to copy your favourite selected audio files – the ones specifically used in your playlist, that usually exist in a deep structure of their own. There seem to be some proprietary tools for it, but here’s a Free (in all senses) and Open Source solution.

I adapted my previous Python scripts (that allow for files to be appended to a playlist on the command line, and shuffle a playlist from the command line) to make a – well – bit of a Frankenstein’s monster that will take a playlist as input, and copy the playlist and all its referenced file to another directory. It will create the required directory structure, but will only copy the actual music files on the playlist.

I have used this successfully to move a playlist and the selected directory structure onto more modest sized music players.

Usage looks something like this.

playlist-copy.py -v -i /path/to/your/list.m3u -o /path/where/you/want/it/all.m3u

Have fun, use with care, and I’ll try to clean it up at some stage. If you break something, you own all the pieces, but feel free to write me a (nice) comment below.

At some point I’ll move these scripts to Python 3 and off the deprecated option handling code module.

#!/usr/bin/env python

#
# Simple script to copy a whole playlist and its contents
# Colin Turner <ct@piglets.com>
# 2016
# GPL v2
#
# Need to move all of these scripts to Python 3 in due course

import random
import re
import os
import shutil
import eyeD3

# We want to be able to process some command line options.
from optparse import OptionParser

def process_lines(options, all_lines):
  'process the list of all playlist lines into three chunks'
  # Eventually we want to support several formats
  m3u = True
  extm3u = False
  if options.verbose:
    print "Read %u lines..." % len(all_lines)
  header = list()
  middle = list()
  footer = list()

  # Check first line for #EXTM3U
  if re.match("^#EXTM3U", all_lines[0]):     if options.verbose:       print "EXTM3U format file..."     extm3u = True     header.append(all_lines[0])     del all_lines[0]    loop = 0   while loop < len(all_lines):     # Each 'item' may be multiline     item = list()     if re.match("^#EXTINF.*", all_lines[loop]):
      item.append(all_lines[loop])
      loop = loop + 1
    # A proper regexp for filenames would be good
    if loop < len(all_lines):
      item.append(all_lines[loop])
      loop = loop + 1
    if options.verbose: print item
    middle.append(item)

  return (header, middle, footer)



def load_playlist(options):
  'loads the playlist into an array of arrays'
  if options.verbose:
    print "Reading playlist %s ..." % options.in_filename
  with open(options.in_filename, 'r') as file:
    all_lines = file.readlines()
  (header, middle, footer) = process_lines(options, all_lines)
  return (header, middle, footer)

def write_playlist(options, header, middle, footer):
  'writes the shuffled playlist'
  if options.verbose:
    print "Writing playlist %s ..." % options.out_filename
  with open(options.out_filename, 'w') as file:
    for line in header:
      file.write(line)
    for item in middle:
        # Get the filename, as it will be
        mp3_filename = resolve_mp3_filename(item[1], options)
        copy_mp3(item[1], mp3_filename, options)
        for line in item:
          # TODO We should wewrite line 1
          file.write(line)
    for line in footer:
      file.write(line)

def copy_mp3(source, destination, options):
  'copys an individual mp3 file, making directories as needed'
  source_playlist_path = os.path.dirname(os.path.abspath(options.in_filename))

  destination_playlist_path = os.path.dirname(os.path.abspath(options.out_filename))

  mp3_source = source_playlist_path + os.path.sep + source
  mp3_destination = destination_playlist_path + os.path.sep + destination

  mp3_destination_dir = os.path.dirname(os.path.abspath(mp3_destination))

  if not os.path.exists(mp3_destination_dir.rstrip()):
      os.makedirs(mp3_destination_dir.rstrip())

  shutil.copyfile(mp3_source.rstrip(), mp3_destination.rstrip())

def copy(options):
  'perform the copy on the playlist'
  # read the existing data into three arrays in a tuple
  (header, middle, footer) = load_playlist(options)
  # and shuffle the lines array
  if options.verbose:
    print "Copying..."
  # now spit them back out
  write_playlist(options, header, middle, footer)


def append(options, artist, title, seconds):
  'append the fetched data to the playlist'
  mp3_filename = resolve_mp3_filename(options)
  # Check if the playlist file exists
  there_is_no_spoon = not os.path.isfile(options.out_filename)

  with open(options.out_filename, 'a+') as playlist:
    # was the file frshly created?
    if there_is_no_spoon:
      # So write the header
      print >> playlist, "#EXTM3U"
    else:
      # There was a file, so check the last character, in case there was no \n
      playlist.seek(-1, os.SEEK_END)
      last_char = playlist.read(1)
      if(last_char != '\n'):
        print >> playlist

    # OK, now able to write
    print >> playlist, "#EXTINF:%u,%s - %s" % (seconds, artist, title)
    print >> playlist, "%s" % mp3_filename


def resolve_mp3_filename(filename, options):
  'resolve the mp3 filename appropriately, if we can, and if we are asked to'
  'there are three modes, depending on command line parameters:'
  '-l we write the filename precisely as on the command list'
  '-r specifies a base relative which to write the filename'
  'otherwise we try to resolve relative to the directory of the playlist'
  'the absolute filename will be the fall back position is resolution is impossible'

  if options.leave_filename:
    # we have been specifcally told not to resolve the filename
    mp3_filename = filename
    if options.verbose:
      print "Filename resolution disabled."

  if not options.leave_filename and not len(options.relative_to):
    # Neither argument used, automatcally resolve relative to the playlist
    (playlist_path, playlist_name) = os.path.split(os.path.abspath(options.out_filename))
    options.relative_to = playlist_path + os.path.sep
    if options.verbose:
      print "Automatic filename resolution relative to playlist base %s" % options.relative_to

  if len(options.relative_to):
    # We have been told to map the path relative to another path
    mp3_filename = os.path.abspath(filename)
    # Check that the root is actually present
    if mp3_filename.find(options.relative_to) == 0:
      # It is present and at the start of the line
      mp3_filename = mp3_filename.replace(options.relative_to, '', 1)

  if options.verbose:
    print "mp3 filename will be written as %s..." % mp3_filename
  return mp3_filename

def get_meta_data(options):
  'perform the append on the playlist'
  # read the existing data into three arrays in a tuple
  if options.verbose:
    print "Opening MP3 file %s ..." % options.in_filename
  if eyeD3.isMp3File(options.in_filename):
    # Ok, so it's an mp3
    audioFile = eyeD3.Mp3AudioFile(options.in_filename)
    tag = audioFile.getTag()
    artist = tag.getArtist()
    title = tag.getTitle()
    seconds = audioFile.getPlayTime()
    if not options.quiet:
      print "%s - %s (%s s)" % (artist, title, seconds)
    # OK, we have the required information, now time to write to the playlist
    return artist, title, seconds
  else:
    print "Not a valid mp3 file."
    exit(1)


def print_banner():
  print "playlist-copy"

def main():
  'the main function that kicks everything else off'

  usage = "usage: %prog [options] arg"
  parser = OptionParser(usage)
  parser.add_option("-i", "--input-file", dest="in_filename",
                    help="read playlist from FILENAME")
  parser.add_option("-o", "--output-file", dest="out_filename",
                    help="write new playlist to FILENAME")
  parser.add_option("-l", "--leave-filename", dest="leave_filename", action="store_false",
                    help="leaves the mp3 path as specified on the command line, rather than resolving it")
  parser.add_option("-r", "--relative-to", dest="relative_to", default="",
                    help="resolves mp3 filename relative to this path")
  parser.add_option("-v", "--verbose",
                    action="store_true", dest="verbose")
  parser.add_option("-q", "--quiet", default=False,
                    action="store_true", dest="quiet")

  (options, args) = parser.parse_args()
#  if len(args) == 0:
#      parser.error("use -h for more help")

  if not options.quiet:
    print_banner()

  copy(options)

  if not options.quiet:
      print "Playlist copy complete..."


if  __name__ == '__main__':
  main()


 

Resistance in Aikido

If you spend a little bit of time on-line looking at what other martial arts practitioners have to say about aikido, one of the thing you note is that people with little or no experience whatsoever about aikido still have plenty to say about it.

The most common comments is that aikido has no sparring and doesn’t train against opponents offering resistance.

Let me first take the latter point. What others assert here is that in aikido dojos people don’t train to execute a technique against someone actively resisting it.

The is a little bit true, a little bit false, and a whole lot completely missing the point.

What people see as training against no resistance is the stage when beginners are learning a technique and need to understand its shape. At this stage the uke, the attacker, who ideally will be a more senior student will indeed sometimes help the other person complete the technique.

This is not dissimilar to early training in other martial arts where people punch thin air, or cut it with bokken.

However, as the student progresses you can expect the uke to be much less of a push over, literally and metaphorically – at least in most dojos. People don’t just fall over when the technique is wrong, there may indeed be some resistance. There will be a touch here and there that could have been a counter strike. How much of this resistance there should be is a point of real controversy. It is possible to find dojos who think it should always be none, and others who think it should be immense and unrelenting.

In the main there is a recognition that it does not demonstrate skill to prevent a technique that you know is coming, and so it’s poor form to do so for its own sake when people are first learning a technique.

For some people this demonstrates the inefficacy of aikido, but it’s no different than suggesting that a person in another art, knowing that a kick to the head is coming, can easily avoid or counter that.

The Way of Harmony

But the main point here is what ai ki do means – the way of harmony with ki of your attacker, or the energy of your attacker. In other words, when an opponent resists, they resist a specific thing. The whole point of aikido is not to force your technique against your opponent’s energy. Yes, you may be able to force it on. But that is not aikido. There are also tricks of the trade that can make a technique work even when it’s probably stupid to apply it, but these should be reserved for moments when you find yourself in the equivalent of having your heels on a cliff edge with tigers below.

So what should an aikidoka do, when they begin technique A and their opponent resists it? Pretty much anything other than A. When you watch someone do aikido badly, you can see this tension all the time, the nage, the defender, has a plan to do technique A. The painful bit is that if A is not possible, the longer it takes nage to work this out, and move on, the movement between attacker and defender jams up, it is actually a point of real vulnerability, not strength for the nage.

So aikido is about learning that the instant A becomes untenable, you let go of it, and move to B, C, and so on. The more someone resists a specific thing, the more vulnerable they become to its opposite.

So coming back to the lack of “sparring”. This is actually also untrue, this kind of practice does occur in many dojos – it’s usually a different kind of sparring – and it’s valuable if controlled, but real aikido happens all the time when the initial technique goes wrong, the challenge is to realise that trying to push through resistance is completely the wrong approach.