Create PLS playlists

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Create a ``.pls`` playlist from music filenames.

Specify a path to be recursively searched for music files.

Requires Python 3.3 and the `pathlib` module, or Python 3.4+.

According to an `unofficial PLS format specification`__, the attribute
``NumberOfEntries`` can be placed *after* all entries.  This allows to
iterate through filenames without keeping details for each entry in
memory.

__ http://forums.winamp.com/showthread.php?threadid=65772

:Copyright: 2007-2016 Jochen Kupperschmidt
:Date: 2016-03-28 (original release: 09-Feb-2007)
:License: MIT
:Website: http://homework.nwsnet.de/releases/1a02/#create-pls-playlists
"""

from argparse import ArgumentParser
from itertools import count
import os.path
from pathlib import Path
import re
from sys import stdout


PATTERN = re.compile('\.(mp3|ogg)$', re.I)


def parse_args():
    """Parse command line arguments."""
    parser = ArgumentParser()
    parser.add_argument('path', type=Path)

    return parser.parse_args()


def find_files(path):
    """Return all matching files beneath the path."""
    for root, dirs, files in os.walk(str(path)):
        for fn in filter(PATTERN.search, files):
            filename = os.path.join(root, fn)
            yield Path(filename)


def generate_playlist(filenames):
    """Generate a PLS playlist from filenames."""
    yield '[playlist]\n\n'

    total = 0

    entry_template = (
        'File{number:d}={file}\n'
        'Title{number:d}={title}\n'
        'Length{number:d}=-1\n\n')

    for track_entry in generate_track_entries(filenames):
        total += 1
        yield entry_template.format(**track_entry)

    yield (
        'NumberOfEntries={:d}\n'
        'Version=2\n').format(total)


def generate_track_entries(filenames, start_number=1):
    """Generate track entries."""
    numbers = count(start_number)
    for number, filename in zip(numbers, filenames):
        yield create_track_entry(number, filename)


def create_track_entry(number, path):
    """Create a track entry."""
    title = path.stem

    return {
        'number': number,
        'file': path,
        'title': title,
    }


if __name__ == '__main__':
    args = parse_args()
    paths = find_files(args.path)
    stdout.writelines(generate_playlist(paths))