Skip to content

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
x1ppy committed Apr 6, 2020
1 parent c37c063 commit 6e670c2
Show file tree
Hide file tree
Showing 6 changed files with 814 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__
260 changes: 258 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,258 @@
# freezetag
extract, strip, and restore music tags and filenames
freezetag
=========

Installation
------------

$> pip install git+https://github.com/x1ppy/freezetag

Usage
-----

~~~
usage: freezetag [-h] command ... [directory]
Saves, strips, and restores file paths and music metadata.
positional arguments:
command
freeze Saves paths and music metadata in directory to a freezetag file.
thaw Restores paths and music metdata in directory from a freezetag file.
shave Strips metadata from all music files in directory.
show Displays the contents of a freezetag file.
directory Directory to process (default: current directory).
optional arguments:
-h, --help show this help message and exit
Use "freezetag [command] --help" for more information about a command.
~~~

Supported Formats
-----------------

Currently, the following music formats are supported:
* FLAC
* MP3

The following metadata formats are supported:
* Vorbis comments
* ID3

Note that metadata will be frozen/thawed/shaved for supported music formats only. Likewise, only supported metadata
formats will be processed.

Examples
--------

First, let's prepare a minimal music set:

$> mkdir beethoven-example && cd beethoven-example
$> wget https://www.mfiles.co.uk/mp3-downloads/Beethoven-Symphony5-1.mp3
$> wget https://www.mfiles.co.uk/mp3-downloads/fur-elise.mp3
$> wget https://www.mfiles.co.uk/mp3-downloads/sugar-plum-fairy.mp3

and check the tags:

$> id3ted -l *
Beethoven-Symphony5-1.mp3
ID3v1:
Title : Symphony No.5 - 1st movement Track: 0
Artist : Ludwig van Beethoven Year :
Album : www.mfiles.co.uk Genre: Classical (32)
Comment: © Music Files Ltd

fur-elise.mp3:
ID3v1:
Title : Fur Elise (album leaf) - Piano Track: 0
Artist : Ludwig van Beethoven Year :
Album : www.mfiles.co.uk Genre: Classical (32)
Comment: © Music Files Ltd

sugar-plum-fairy.mp3:
ID3v1:
Title : Sugar Plum Fairy (Nutcracker) Track: 0
Artist : Peter Tchaikovsky Year :
Album : www.mfiles.co.uk Genre: Classical (32)
Comment: © Music Files Ltd

### freeze

To kick things off, run `freezetag freeze`:

$> freezetag freeze
455d965a07357826e76118a3c84ffbac0463fde0 Beethoven-Symphony5-1.mp3
6f860819575aa0bef153b9d1477030bb9f91d27d fur-elise.mp3
6fa39dfda7512e26a6dca13579a7739279e1d193 sugar-plum-fairy.mp3
freezetag created at /home/x1ppy/beethoven-example/Ff347f70000e23124-863f2c3d-2675d4d7.ftag

If you check the contents of the directory, nothing has changed other than the new file
`Ff347f70000e23124-863f2c3d-2675d4d7.ftag`.

### thaw

Now, let's make things interesting by modifying our files:

$> mv Beethoven-Symphony5-1.mp3 01-symphany.mp3
$> mv fur-elise.mp3 02-fur-elise.mp3
$> mv sugar-plum-fairy.mp3 03-sugar-plum-fairy.mp3
$> id3ted 01-symphany.mp3 -A "Custom Album" -y 1808 -T 1
$> id3ted 02-fur-elise.mp3 -A "Custom Album" -y 1808 -T 2
$> id3ted 03-sugar-plum-fairy.mp3 -A "Custom Album" -y 1808 -T 3

and checking the tags again:

$> id3ted -l *
01-symphany.mp3:
ID3v1:
Title : Symphony No.5 - 1st movement Track: 1
Artist : Ludwig van Beethoven Year : 1808
Album : Custom Album Genre: Classical (32)
Comment: © Music Files Ltd

02-fur-elise.mp3:
ID3v1:
Title : Fur Elise (album leaf) - Piano Track: 2
Artist : Ludwig van Beethoven Year : 1808
Album : Custom Album Genre: Classical (32)
Comment: © Music Files Ltd

03-sugar-plum-fairy.mp3:
ID3v1:
Title : Sugar Plum Fairy (Nutcracker) Track: 3
Artist : Peter Tchaikovsky Year : 1808
Album : Custom Album Genre: Classical (32)
Comment: © Music Files Ltd

At this point, we've renamed and retagged our album -- all standard stuff in the music world. At this point, our
original tags and filenames are long gone, so if we wanted to reset the tags and filenames (to share with others, for
example), we'd have to redownload the original files again. Or do we?

Let's run `freezetag thaw`:

$> freezetag thaw
455d965a07357826e76118a3c84ffbac0463fde0 Beethoven-Symphony5-1.mp3
thawing /x1ppy/beethoven-example/01-symphany.mp3
6f860819575aa0bef153b9d1477030bb9f91d27d fur-elise.mp3
thawing /x1ppy/beethoven-example/02-fur-elise.mp3
6fa39dfda7512e26a6dca13579a7739279e1d193 sugar-plum-fairy.mp3
thawing /x1ppy/beethoven-example/03-sugar-plum-fairy.mp3

Now, if we list the directory contents, we see that the files are back to their original names:

$> ls
Beethoven-Symphony5-1.mp3 Ff347f70000e23124-863f2c3d-2675d4d7.ftag fur-elise.mp3 sugar-plum-fairy.mp3

And if we check the tags one last time:

$> id3ted -l *
Beethoven-Symphony5-1.mp3:
ID3v1:
Title : Symphony No.5 - 1st movement Track: 0
Artist : Ludwig van Beethoven Year :
Album : www.mfiles.co.uk Genre: Classical (32)
Comment: © Music Files Ltd

fur-elise.mp3:
ID3v1:
Title : Fur Elise (album leaf) - Piano Track: 0
Artist : Ludwig van Beethoven Year :
Album : www.mfiles.co.uk Genre: Classical (32)
Comment: © Music Files Ltd

sugar-plum-fairy.mp3:
ID3v1:
Title : Sugar Plum Fairy (Nutcracker) Track: 0
Artist : Peter Tchaikovsky Year :
Album : www.mfiles.co.uk Genre: Classical (32)
Comment: © Music Files Ltd

we see that everything is back to its original, untouched state.

### shave

Strips all metadata from music files in the directory.

Before:

$> id3ted -l *
Beethoven-Symphony5-1.mp3:
ID3v1:
Title : Symphony No.5 - 1st movement Track: 0
Artist : Ludwig van Beethoven Year :
Album : www.mfiles.co.uk Genre: Classical (32)
Comment: © Music Files Ltd

fur-elise.mp3:
ID3v1:
Title : Fur Elise (album leaf) - Piano Track: 0
Artist : Ludwig van Beethoven Year :
Album : www.mfiles.co.uk Genre: Classical (32)
Comment: © Music Files Ltd

sugar-plum-fairy.mp3:
ID3v1:
Title : Sugar Plum Fairy (Nutcracker) Track: 0
Artist : Peter Tchaikovsky Year :
Album : www.mfiles.co.uk Genre: Classical (32)
Comment: © Music Files Ltd

After:

$> freezetag shave
Beethoven-Symphony5-1.mp3
shaved ID3v1 (128)
fur-elise.mp3
shaved ID3v1 (128)
sugar-plum-fairy.mp3
shaved ID3v1 (128)
$> id3ted -l *
(no output)

`freezetag shave` can be useful if you want to share the "bare" music files. This has the advantage of smaller overall
distribution size (especially if the music contains images), and it also has the advantage of separation of concerns.
That is, users can create and distribute their own freezetag files with bare music files, and the bare music files will
remain unchanged.

### show

Shows the contents of a freezetag file.

$> freezetag show
version: 1
id: Ff347f70000e23124-863f2c3d-2675d4d7
root: beethoven-example
455d965a07357826e76118a3c84ffbac0463fde0 Beethoven-Symphony5-1.mp3
6f860819575aa0bef153b9d1477030bb9f91d27d fur-elise.mp3
6fa39dfda7512e26a6dca13579a7739279e1d193 sugar-plum-fairy.mp3

You can also use the `--json` flag to get parse-friendly output:

$> freezetag show --json
{'files': [{'checksum': '455d965a07357826e76118a3c84ffbac0463fde0',
'path': 'Beethoven-Symphony5-1.mp3'},
{'checksum': '6f860819575aa0bef153b9d1477030bb9f91d27d',
'path': 'fur-elise.mp3'},
{'checksum': '6fa39dfda7512e26a6dca13579a7739279e1d193',
'path': 'sugar-plum-fairy.mp3'}],
'id': 'Ff347f70000e23124-863f2c3d-2675d4d7',
'root': 'beethoven-example',
'version': 1}

Freezetag ID
------------

By default, the freezetag file will be saved to the processed directory as F**a**-**b**-**c**.ftag, where:
* **a** is a 16-character hex string that uniquely identifies the music
* **b** is an 8-character hex string that uniquely identifies the metadata
* **c** is an 8-character hex string that uniquely identifies the freezetag

The **a** and **b** segments do not change between freezes if any paths change or if any non-music files are modified.
Additionally, the **a** segment doesn't change if the music files are retagged.

This means if two different .ftag files have the same **a** segment, they represent the same set of raw music. If they
have both the same **a** and **b** segments, they represent the same set of raw music *and* their metadata.

If all three segments are the same, the two freezetag files are identical. Therefore, running `freezetag freeze` twice
will only result in a single freezetag file being created: since the ID will stay the same, the existing freezetag file
will be replaced on the second invocation.
51 changes: 51 additions & 0 deletions formats/flac.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from construct import *


BLOCK_TYPES = [
'STREAMINFO',
'PADDING',
'APPLICATION',
'SEEKTABLE',
'VORBIS_COMMENT',
'CUESHEET',
'PICTURE',
]

FlacMetadataFormat = Struct(
'info' / BitStruct(
'last' / Flag,
'block_type' / BitsInteger(7),
),
'size' / Int24ub,
'data' / Bytes(this.size),
)

FlacFormat = Struct(
'signature' / Const(b'fLaC'),
'metadata' / RepeatUntil(lambda metadata, *_: metadata.info.last, FlacMetadataFormat),
'audio' / GreedyBytes,
)

class FlacFile:
extension = '.flac'
format = FlacFormat
metadata_format = PrefixedArray(Int8ub, FlacMetadataFormat)

def iter_metadata(metadata):
for item in metadata:
yield BLOCK_TYPES[item.info.block_type], item.size

def __init__(self, file_bytes):
self.instance = FlacFormat.parse(file_bytes)

def strip(self):
streaminfo = self.instance.metadata[0]
streaminfo.info.last = True
metadata = self.instance.metadata[1:]
self.instance.metadata = [streaminfo]
return metadata

def restore_metadata(self, metadata):
# Append the frozen metadata to the stripped media.
self.instance.metadata[0].info.last = not len(metadata)
self.instance.metadata += metadata
Loading

0 comments on commit 6e670c2

Please sign in to comment.