Example #1
0
// HasID3V1Tag returns true if an ID3v1 tag is present at the end of r.
func HasID3v1Tag(r types.SizeReaderAt) (bool, error) {
	if r.Size() < ID3v1TagLength {
		return false, nil
	}

	buf := make([]byte, len(id3v1Magic), len(id3v1Magic))
	if _, err := r.ReadAt(buf, r.Size()-ID3v1TagLength); err != nil {
		return false, fmt.Errorf("Failed to read ID3v1 data: %v", err)
	}
	if bytes.Equal(buf, id3v1Magic) {
		return true, nil
	}
	return false, nil
}
Example #2
0
// indexMusic adds mutations to index the wholeRef by attached metadata and other properties.
func indexMusic(r types.SizeReaderAt, wholeRef blob.Ref, mm *mutationMap) {
	tag, err := taglib.Decode(r, r.Size())
	if err != nil {
		log.Print("index: error parsing tag: ", err)
		return
	}

	var footerLength int64 = 0
	if hasTag, err := media.HasID3v1Tag(r); err != nil {
		log.Print("index: unable to check for ID3v1 tag: ", err)
		return
	} else if hasTag {
		footerLength = media.ID3v1TagLength
	}

	// Generate a hash of the audio portion of the file (i.e. excluding ID3v1 and v2 tags).
	audioStart := int64(tag.TagSize())
	audioSize := r.Size() - audioStart - footerLength
	hash := sha1.New()
	if _, err := io.Copy(hash, io.NewSectionReader(r, audioStart, audioSize)); err != nil {
		log.Print("index: error generating SHA1 from audio data: ", err)
		return
	}
	mediaRef := blob.RefFromHash(hash)

	duration, err := media.GetMPEGAudioDuration(io.NewSectionReader(r, audioStart, audioSize))
	if err != nil {
		log.Print("index: unable to calculate audio duration: ", err)
		duration = 0
	}

	var yearStr, trackStr, discStr, durationStr string
	if !tag.Year().IsZero() {
		const justYearLayout = "2006"
		yearStr = tag.Year().Format(justYearLayout)
	}
	if tag.Track() != 0 {
		trackStr = fmt.Sprintf("%d", tag.Track())
	}
	if tag.Disc() != 0 {
		discStr = fmt.Sprintf("%d", tag.Disc())
	}
	if duration != 0 {
		durationStr = fmt.Sprintf("%d", duration/time.Millisecond)
	}

	// Note: if you add to this map, please update
	// pkg/search/query.go's MediaTagConstraint Tag docs.
	tags := map[string]string{
		"title":              tag.Title(),
		"artist":             tag.Artist(),
		"album":              tag.Album(),
		"genre":              tag.Genre(),
		"musicbrainzalbumid": tag.CustomFrames()["MusicBrainz Album Id"],
		"year":               yearStr,
		"track":              trackStr,
		"disc":               discStr,
		"mediaref":           mediaRef.String(),
		"durationms":         durationStr,
	}

	for tag, value := range tags {
		if value != "" {
			mm.Set(keyMediaTag.Key(wholeRef, tag), keyMediaTag.Val(value))
		}
	}
}
Example #3
0
// GetMPEGAudioDuration reads the first frame in r and returns the audio length with millisecond precision.
// Format details are at http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header.
func GetMPEGAudioDuration(r types.SizeReaderAt) (time.Duration, error) {
	var header uint32
	if err := binary.Read(io.NewSectionReader(r, 0, r.Size()), binary.BigEndian, &header); err != nil {
		return 0, fmt.Errorf("Failed to read MPEG frame header: %v", err)
	}
	getBits := func(startBit, numBits uint) uint32 {
		return (header << startBit) >> (32 - numBits)
	}

	if getBits(0, 11) != 0x7ff {
		return 0, errors.New("Missing sync bits in MPEG frame header")
	}
	var version mpegVersion
	var ok bool
	if version, ok = mpegVersionsById[getBits(11, 2)]; !ok {
		return 0, errors.New("Invalid MPEG version index")
	}
	var layer mpegLayer
	if layer, ok = mpegLayersByIndex[getBits(13, 2)]; !ok {
		return 0, errors.New("Invalid MPEG layer index")
	}
	bitrate := mpegBitrates[version][layer][getBits(16, 4)]
	if bitrate == 0 {
		return 0, errors.New("Invalid MPEG bitrate")
	}
	samplingRate := mpegSamplingRates[version][getBits(20, 2)]
	if samplingRate == 0 {
		return 0, errors.New("Invalid MPEG sample rate")
	}
	samplesPerFrame := mpegSamplesPerFrame[version][layer]

	var xingHeaderStart int64 = 4
	// Skip "side information".
	if getBits(24, 2) == 0x3 { // Channel mode; 0x3 is mono.
		xingHeaderStart += 17
	} else {
		xingHeaderStart += 32
	}
	// Skip 16-bit CRC if present.
	if getBits(15, 1) == 0x0 { // 0x0 means "has protection".
		xingHeaderStart += 2
	}

	b := make([]byte, 12, 12)
	if _, err := r.ReadAt(b, xingHeaderStart); err != nil {
		return 0, fmt.Errorf("Unable to read Xing header at %d: %v", xingHeaderStart, err)
	}
	var ms int64
	if bytes.Equal(b[0:4], xingHeaderName) || bytes.Equal(b[0:4], infoHeaderName) {
		r := bytes.NewReader(b[4:])
		var xingFlags uint32
		binary.Read(r, binary.BigEndian, &xingFlags)
		if xingFlags&0x1 == 0x0 {
			return 0, fmt.Errorf("Xing header at %d lacks number of frames", xingHeaderStart)
		}
		var numFrames uint32
		binary.Read(r, binary.BigEndian, &numFrames)
		ms = int64(samplesPerFrame) * int64(numFrames) * 1000 / int64(samplingRate)
	} else {
		// Okay, no Xing VBR header. Assume that the file has a constant bitrate.
		// (The other alternative is to read the whole file and examine each frame.)
		ms = r.Size() / int64(bitrate) * 8
	}
	return time.Duration(ms) * time.Millisecond, nil
}