Skip to content

Instantly share code, notes, and snippets.

@mhthies
Last active January 15, 2017 12:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mhthies/aef0cc7d793b09a22a429b4c15492075 to your computer and use it in GitHub Desktop.
Save mhthies/aef0cc7d793b09a22a429b4c15492075 to your computer and use it in GitHub Desktop.
Python script to copy all files contained in an (url encoded) m3u playlist into a different directory. (Preserving original directory structure or prefixing filenames with index.)
#!/usr/bin/env python3
import shutil
import os
import argparse
import urllib.parse
import math
# Parse command line arguments
parser = argparse.ArgumentParser(description='Copy all music files listed in an m3u file to a destination directory.')
parser.add_argument('playlist', type=str, help='The m3u playlist file')
parser.add_argument('destination', type=str, help='The destination directory')
parser.add_argument('-s', '--source', type=str, help='The source root directory. Relative paths in playlist will be '
'searched from there. It is also considered as root of the directory '
'tree.', default=os.path.abspath(os.sep))
parser.add_argument('-t', '--tree', action='store_const', const=True, default=False,
help='Preserve folder tree structure. If set the folder tree inside source root will be build up '
'inside the destination directory. Otherwise the files will be copied to the destination '
'folder and prefixed with a couter.')
parser.add_argument('-m', '--no-meta', action='store_const', const=True, default=False,
help='Only copy file contents, no meta data. This should fix errors when copying to filesystems '
'without chmod support (e.g. MTP devices).')
parser.add_argument('-v', '--verbose', action='count',
help='More verbose output: List every copied and already existing file')
parser.add_argument('-n', '--dry-run', action='store_const', const=True, default=False,
help='Read the playlist and check for file existence but do not touch any file.')
args = parser.parse_args()
# Read playlist file
# Based on https://github.com/smoqadam/m3u-file-collector
files = []
with open(args.playlist) as f:
for line in f :
line = line.strip()
if line and line[0] != '#':
files.append(urllib.parse.unquote(line))
print('Read {} file paths from playlist.'.format(len(files)))
for i,f in enumerate(files):
print("Checking file {}/{} ... \033[F".format(i+1, len(files)))
src = f if os.path.isabs(f) else os.path.realpath(os.path.join(args.source, f))
if not os.path.exists(src):
print("\"{}\" was not found in source directory.".format(f))
continue
if args.tree:
relative = os.path.relpath(src, args.source)
if not relative or relative.startswith(os.pardir):
print("\"{}\" seems to be outside source directory.".format(f))
continue
os.makedirs(os.path.join(args.destination, os.path.dirname(relative)), exist_ok=True)
dest = os.path.join(args.destination, relative)
else:
dest = os.path.join(args.destination,
"{num:0{width}d}_{name}".format(num=i,
width=math.ceil(math.log10(len(files))),
name=os.path.basename(f)))
if (os.path.exists(dest) and round(os.path.getmtime(src)) <= round(os.path.getmtime(dest))):
if args.verbose:
print("\"{}\" is already up to date.".format(f))
continue
if args.verbose:
print("\"{}\" will be copied to \"{}\".".format(src, dest))
if not args.dry_run:
print("Copying file {}/{} ... \033[F".format(i+1, len(files)))
if not args.no_meta:
shutil.copy2(src, dest)
else:
shutil.copyfile(src, dest)
print("Finished. ")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment