xnde/
tracks.rs

1// Copyright (C) 2020-2023 Michael Herstine <sp1ff@pobox.com>
2//
3// This file is part of xnde.
4//
5// xnde is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// xnde is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with xnde.  If not, see <http://www.gnu.org/licenses/>. *
17//! Track type
18//!
19//! # Introduction
20//!
21//! This module introduces the [`Track`] struct, which represents a single track in your Winamp
22//! Music Library. The idea is to map each record in the NDE "main" table to a [`Track`] instance.
23//! [`Track`] derives the [`Serialize`] [`Serde`] trait, making it easy to write to file.
24//!
25//! [`Track`]: struct.Track.html
26//! [`Serialize`]: https://docs.serde.rs/serde/trait.Serialize.html
27//! [`Serde`]: https://docs.serde.rs
28//!
29//! # Discussion
30//!
31//! This is probably the module with which I am the least satisfied. The design problem is:
32//!
33//! 1. the attributes of a track shall be derived at runtime from the columns in the Music
34//!    Library Database main table rather than fixed at compile time.
35//!
36//! 2. even at runtime, within the table, not every column is guaranteed to appear in any
37//!    given record
38//!
39//! I solved this by first building a mapping from column ID to track attributes (when reading the
40//! first record of the table). For each subsequent record, that lets me map each field (for which I
41//! have a column ID) to the corresponding track attribute. I use a per-record map of
42//! [`TrackAttribute`] to [`FieldValue`] to keep track of what I've seen so far (since I don't want
43//! to count even on the fields appearing in the same order). Finally, once I've parsed the entire
44//! record, I read the elements out & into the new [`Track`] instance.
45//!
46//! The basic design isn't awful, but the implementation code is prolix & inelegant. Suggestions
47//! [welcome](mailto:sp1ff@pobox.com).
48//!
49//! [`TrackAttribute`]: enum.TrackAttribute.html
50//! [`FieldValue`]: enum.FieldValue.html
51//! [`Track`]: struct.Track.html
52
53use crate::fields::{ColumnField, FieldValue, NdeField};
54
55use log::error;
56use parse_display::Display;
57use serde::Serialize;
58
59use std::collections::HashMap;
60
61////////////////////////////////////////////////////////////////////////////////////////////////////
62//                                           error type                                           //
63////////////////////////////////////////////////////////////////////////////////////////////////////
64
65#[derive(Debug, Display)]
66pub enum Cause {
67    /// An error in another crate or  module-- cf. source.
68    #[display("An error in another crate or  module-- cf. source.")]
69    Other,
70    /// No filename field found
71    #[display("No filename field found.")]
72    NoFilename,
73}
74
75#[derive(Debug, Display)]
76#[display("{cause} Source (if any): {source} Stack trace (if any): {trace}")]
77pub struct Error {
78    /// Enumerated status code
79    #[display("XNDE error {}.")]
80    cause: Cause,
81    // This is an Option that may contain a Box containing something that implements
82    // std::error::Error.  It is still unclear to me how this satisfies the lifetime bound in
83    // std::error::Error::source, which additionally mandates that the boxed thing have 'static
84    // lifetime. There is a discussion of this at
85    // <https://users.rust-lang.org/t/what-does-it-mean-to-return-dyn-error-static/37619/6>,
86    // but at the time of this writing, i cannot follow it.
87    // TODO(sp1ff): figure out how to format `source'
88    #[display("fields error caused by {:#?}.")]
89    source: Option<Box<dyn std::error::Error>>,
90    /// Optional backtrace
91    // TODO(sp1ff): figure out how to format `source'
92    #[display("backtrace: {:#?}.")]
93    trace: Option<backtrace::Backtrace>,
94}
95
96impl Error {
97    fn new(cause: Cause) -> Error {
98        // TODO(sp1ff): can I trim this frame off the stack trace?
99        Error {
100            cause: cause,
101            source: None,
102            trace: Some(backtrace::Backtrace::new()),
103        }
104    }
105}
106
107impl std::error::Error for Error {
108    /// The lower-level source of this error, if any.
109    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
110        match &self.source {
111            // This is an Option that may contain a reference to something that implements
112            // std::error::Error and has lifetime 'static. I'm still not sure what 'static means,
113            // exactly, but at the time of this writing, I take it to mean a thing which can, if
114            // needed, last for the program lifetime (e.g. it contains no references to anything
115            // that itself does not have 'static lifetime)
116            Some(bx) => Some(bx.as_ref()),
117            None => None,
118        }
119    }
120}
121
122pub type Result<T> = std::result::Result<T, Error>;
123
124////////////////////////////////////////////////////////////////////////////////////////////////////
125
126/// Enumerated set of attributes which Track may include
127#[derive(Debug, Eq, Hash, PartialEq)]
128pub enum TrackAttrs {
129    Filename,
130    Artist,
131    Title,
132    Album,
133    Year,
134    Genre,
135    Comment,
136    TrackNo,
137    Length,
138    Type,
139    LastUpd,
140    LastPlay,
141    Rating,
142    Tuid2,
143    PlayCount,
144    Filetime,
145    Filesize,
146    Bitrate,
147    Disc,
148    Albumartist,
149    ReplaygainAlbumGain,
150    ReplaygainTrackGain,
151    Publisher,
152    Composer,
153    Bpm,
154    Discs,
155    Tracks,
156    IsPodcast,
157    PodcastChannel,
158    PodcastPubdate,
159    GracenoteFileId,
160    GracenoteExtData,
161    Lossless,
162    Category,
163    Codec,
164    Director,
165    Producer,
166    Width,
167    Height,
168    MimeType,
169    DateAdded,
170}
171
172/// Map NDE table columns (discovered at runtime) to Track attributes (fixed at compile-time)
173pub type ColumnMap = HashMap<i32, TrackAttrs>;
174
175/// Build a ColumnMap from the columns in a table's first record
176pub fn new_column_map<'a, CI>(cols: CI) -> ColumnMap
177where
178    CI: Iterator<Item = &'a ColumnField>,
179{
180    let mut col_map: HashMap<i32, TrackAttrs> = HashMap::new();
181    for col in cols {
182        let id = col.id();
183        match col.name().as_str() {
184            "filename" => {
185                col_map.insert(id, TrackAttrs::Filename);
186            }
187            "artist" => {
188                col_map.insert(id, TrackAttrs::Artist);
189            }
190            "title" => {
191                col_map.insert(id, TrackAttrs::Title);
192            }
193            "album" => {
194                col_map.insert(id, TrackAttrs::Album);
195            }
196            "year" => {
197                col_map.insert(id, TrackAttrs::Year);
198            }
199            "genre" => {
200                col_map.insert(id, TrackAttrs::Genre);
201            }
202            "comment" => {
203                col_map.insert(id, TrackAttrs::Comment);
204            }
205            "trackno" => {
206                col_map.insert(id, TrackAttrs::TrackNo);
207            }
208            "length" => {
209                col_map.insert(id, TrackAttrs::Length);
210            }
211            "type" => {
212                col_map.insert(id, TrackAttrs::Type);
213            }
214            "lastupd" => {
215                col_map.insert(id, TrackAttrs::LastUpd);
216            }
217            "lastplay" => {
218                col_map.insert(id, TrackAttrs::LastPlay);
219            }
220            "rating" => {
221                col_map.insert(id, TrackAttrs::Rating);
222            }
223            "tuid2" => {
224                col_map.insert(id, TrackAttrs::Tuid2);
225            }
226            "playcount" => {
227                col_map.insert(id, TrackAttrs::PlayCount);
228            }
229            "filetime" => {
230                col_map.insert(id, TrackAttrs::Filetime);
231            }
232            "filesize" => {
233                col_map.insert(id, TrackAttrs::Filesize);
234            }
235            "bitrate" => {
236                col_map.insert(id, TrackAttrs::Bitrate);
237            }
238            "disc" => {
239                col_map.insert(id, TrackAttrs::Disc);
240            }
241            "albumartist" => {
242                col_map.insert(id, TrackAttrs::Albumartist);
243            }
244            "replaygain_album_gain" => {
245                col_map.insert(id, TrackAttrs::ReplaygainAlbumGain);
246            }
247            "replaygain_track_gain" => {
248                col_map.insert(id, TrackAttrs::ReplaygainTrackGain);
249            }
250            "publisher" => {
251                col_map.insert(id, TrackAttrs::Publisher);
252            }
253            "composer" => {
254                col_map.insert(id, TrackAttrs::Composer);
255            }
256            "bpm" => {
257                col_map.insert(id, TrackAttrs::Bpm);
258            }
259            "discs" => {
260                col_map.insert(id, TrackAttrs::Discs);
261            }
262            "tracks" => {
263                col_map.insert(id, TrackAttrs::Tracks);
264            }
265            "ispodcast" => {
266                col_map.insert(id, TrackAttrs::IsPodcast);
267            }
268            "podcastchannel" => {
269                col_map.insert(id, TrackAttrs::PodcastChannel);
270            }
271            "podcastpubdate" => {
272                col_map.insert(id, TrackAttrs::PodcastPubdate);
273            }
274            "GracenoteFileID" => {
275                col_map.insert(id, TrackAttrs::GracenoteFileId);
276            }
277            "GracenoteExtData" => {
278                col_map.insert(id, TrackAttrs::GracenoteExtData);
279            }
280            "lossless" => {
281                col_map.insert(id, TrackAttrs::Lossless);
282            }
283            "category" => {
284                col_map.insert(id, TrackAttrs::Category);
285            }
286            "codec" => {
287                col_map.insert(id, TrackAttrs::Codec);
288            }
289            "director" => {
290                col_map.insert(id, TrackAttrs::Director);
291            }
292            "producer" => {
293                col_map.insert(id, TrackAttrs::Producer);
294            }
295            "width" => {
296                col_map.insert(id, TrackAttrs::Width);
297            }
298            "height" => {
299                col_map.insert(id, TrackAttrs::Height);
300            }
301            "mimetype" => {
302                col_map.insert(id, TrackAttrs::MimeType);
303            }
304            "dateadded" => {
305                col_map.insert(id, TrackAttrs::DateAdded);
306            }
307            _ => (),
308        }
309    }
310    col_map
311}
312
313/// Winamp Music Library track
314#[derive(Debug, Serialize)]
315pub struct Track {
316    filename: std::path::PathBuf,
317    artist: Option<String>,
318    title: Option<String>,
319    album: Option<String>,
320    year: Option<i32>,
321    genre: Option<String>,
322    comment: Option<String>,
323    trackno: Option<i32>,
324    length: Option<i32>,
325    ttype: Option<i32>,
326    lastupd: Option<i32>,
327    lastplay: Option<i32>,
328    rating: Option<i32>,
329    tuid2: Option<String>,
330    play_count: Option<i32>,
331    filetime: Option<i32>,
332    filesize: Option<i64>,
333    bitrate: Option<i32>,
334    disc: Option<i32>,
335    albumartist: Option<String>,
336    replaygain_album_gain: Option<String>,
337    replaygain_track_gain: Option<String>,
338    publisher: Option<String>,
339    composer: Option<String>,
340    bpm: Option<i32>,
341    discs: Option<i32>,
342    tracks: Option<i32>,
343    is_podcast: Option<i32>,
344    podcast_channel: Option<String>,
345    podcast_pubdate: Option<i32>,
346    gracenote_file_id: Option<String>,
347    gracenote_ext_data: Option<String>,
348    lossless: Option<i32>,
349    category: Option<String>,
350    codec: Option<String>,
351    director: Option<String>,
352    producer: Option<String>,
353    width: Option<i32>,
354    height: Option<i32>,
355    mimetype: Option<String>,
356    date_added: Option<i32>,
357}
358
359impl Track {
360    pub fn new<'a, FI>(col_map: &ColumnMap, fields: FI) -> Result<Track>
361    where
362        FI: Iterator<Item = &'a Box<dyn NdeField>>,
363    {
364        // build a map `attrs_map' from TrackAttrs to fields
365        let mut attrs_map: HashMap<TrackAttrs, crate::fields::FieldValue> = HashMap::new();
366
367        for field in fields {
368            match col_map.get(&field.id()) {
369                Some(attr) => match (attr, field.value()) {
370                    (TrackAttrs::Filename, FieldValue::Filename(x)) => {
371                        attrs_map.insert(TrackAttrs::Filename, FieldValue::Filename(x));
372                    }
373                    (TrackAttrs::Artist, FieldValue::String(x)) => {
374                        attrs_map.insert(TrackAttrs::Artist, FieldValue::String(x));
375                    }
376                    (TrackAttrs::Title, FieldValue::String(x)) => {
377                        attrs_map.insert(TrackAttrs::Title, FieldValue::String(x));
378                    }
379                    (TrackAttrs::Album, FieldValue::String(x)) => {
380                        attrs_map.insert(TrackAttrs::Album, FieldValue::String(x));
381                    }
382                    (TrackAttrs::Year, FieldValue::Integer(x)) => {
383                        attrs_map.insert(TrackAttrs::Year, FieldValue::Integer(x));
384                    }
385                    (TrackAttrs::Genre, FieldValue::String(x)) => {
386                        attrs_map.insert(TrackAttrs::Genre, FieldValue::String(x));
387                    }
388                    (TrackAttrs::Comment, FieldValue::String(x)) => {
389                        attrs_map.insert(TrackAttrs::Comment, FieldValue::String(x));
390                    }
391                    (TrackAttrs::TrackNo, FieldValue::Integer(x)) => {
392                        attrs_map.insert(TrackAttrs::TrackNo, FieldValue::Integer(x));
393                    }
394                    (TrackAttrs::Length, FieldValue::Length(x)) => {
395                        attrs_map.insert(TrackAttrs::Length, FieldValue::Integer(x));
396                    }
397                    (TrackAttrs::Type, FieldValue::Integer(x)) => {
398                        attrs_map.insert(TrackAttrs::Type, FieldValue::Integer(x));
399                    }
400                    (TrackAttrs::LastUpd, FieldValue::Datetime(x)) => {
401                        attrs_map.insert(TrackAttrs::LastUpd, FieldValue::Datetime(x));
402                    }
403                    (TrackAttrs::LastPlay, FieldValue::Datetime(x)) => {
404                        attrs_map.insert(TrackAttrs::LastPlay, FieldValue::Datetime(x));
405                    }
406                    (TrackAttrs::Rating, FieldValue::Integer(x)) => {
407                        attrs_map.insert(TrackAttrs::Rating, FieldValue::Integer(x));
408                    }
409                    (TrackAttrs::Tuid2, FieldValue::String(x)) => {
410                        attrs_map.insert(TrackAttrs::Tuid2, FieldValue::String(x));
411                    }
412                    (TrackAttrs::PlayCount, FieldValue::Integer(x)) => {
413                        attrs_map.insert(TrackAttrs::PlayCount, FieldValue::Integer(x));
414                    }
415                    (TrackAttrs::Filetime, FieldValue::Datetime(x)) => {
416                        attrs_map.insert(TrackAttrs::Filetime, FieldValue::Integer(x));
417                    }
418                    (TrackAttrs::Filesize, FieldValue::Int64(x)) => {
419                        attrs_map.insert(TrackAttrs::Filesize, FieldValue::Int64(x));
420                    }
421                    (TrackAttrs::Bitrate, FieldValue::Integer(x)) => {
422                        attrs_map.insert(TrackAttrs::Bitrate, FieldValue::Integer(x));
423                    }
424                    (TrackAttrs::Disc, FieldValue::Integer(x)) => {
425                        attrs_map.insert(TrackAttrs::Disc, FieldValue::Integer(x));
426                    }
427                    (TrackAttrs::Albumartist, FieldValue::String(x)) => {
428                        attrs_map.insert(TrackAttrs::Albumartist, FieldValue::String(x));
429                    }
430                    (TrackAttrs::ReplaygainAlbumGain, FieldValue::String(x)) => {
431                        attrs_map.insert(TrackAttrs::ReplaygainAlbumGain, FieldValue::String(x));
432                    }
433                    (TrackAttrs::ReplaygainTrackGain, FieldValue::String(x)) => {
434                        attrs_map.insert(TrackAttrs::ReplaygainTrackGain, FieldValue::String(x));
435                    }
436                    (TrackAttrs::Publisher, FieldValue::String(x)) => {
437                        attrs_map.insert(TrackAttrs::Publisher, FieldValue::String(x));
438                    }
439                    (TrackAttrs::Composer, FieldValue::String(x)) => {
440                        attrs_map.insert(TrackAttrs::Composer, FieldValue::String(x));
441                    }
442                    (TrackAttrs::Bpm, FieldValue::Integer(x)) => {
443                        attrs_map.insert(TrackAttrs::Bpm, FieldValue::Integer(x));
444                    }
445                    (TrackAttrs::Discs, FieldValue::Integer(x)) => {
446                        attrs_map.insert(TrackAttrs::Discs, FieldValue::Integer(x));
447                    }
448                    (TrackAttrs::Tracks, FieldValue::Integer(x)) => {
449                        attrs_map.insert(TrackAttrs::Tracks, FieldValue::Integer(x));
450                    }
451                    (TrackAttrs::IsPodcast, FieldValue::Integer(x)) => {
452                        attrs_map.insert(TrackAttrs::IsPodcast, FieldValue::Integer(x));
453                    }
454                    (TrackAttrs::PodcastChannel, FieldValue::String(x)) => {
455                        attrs_map.insert(TrackAttrs::PodcastChannel, FieldValue::String(x));
456                    }
457                    (TrackAttrs::PodcastPubdate, FieldValue::Integer(x)) => {
458                        attrs_map.insert(TrackAttrs::PodcastPubdate, FieldValue::Integer(x));
459                    }
460                    (TrackAttrs::GracenoteFileId, FieldValue::String(x)) => {
461                        attrs_map.insert(TrackAttrs::GracenoteFileId, FieldValue::String(x));
462                    }
463                    (TrackAttrs::GracenoteExtData, FieldValue::String(x)) => {
464                        attrs_map.insert(TrackAttrs::GracenoteExtData, FieldValue::String(x));
465                    }
466                    (TrackAttrs::Lossless, FieldValue::Integer(x)) => {
467                        attrs_map.insert(TrackAttrs::Lossless, FieldValue::Integer(x));
468                    }
469                    (TrackAttrs::Category, FieldValue::String(x)) => {
470                        attrs_map.insert(TrackAttrs::Category, FieldValue::String(x));
471                    }
472                    (TrackAttrs::Codec, FieldValue::String(x)) => {
473                        attrs_map.insert(TrackAttrs::Codec, FieldValue::String(x));
474                    }
475                    (TrackAttrs::Director, FieldValue::String(x)) => {
476                        attrs_map.insert(TrackAttrs::Director, FieldValue::String(x));
477                    }
478                    (TrackAttrs::Producer, FieldValue::String(x)) => {
479                        attrs_map.insert(TrackAttrs::Producer, FieldValue::String(x));
480                    }
481                    (TrackAttrs::Width, FieldValue::Integer(x)) => {
482                        attrs_map.insert(TrackAttrs::Width, FieldValue::Integer(x));
483                    }
484                    (TrackAttrs::Height, FieldValue::Integer(x)) => {
485                        attrs_map.insert(TrackAttrs::Height, FieldValue::Integer(x));
486                    }
487                    (TrackAttrs::MimeType, FieldValue::String(x)) => {
488                        attrs_map.insert(TrackAttrs::MimeType, FieldValue::String(x));
489                    }
490                    (TrackAttrs::DateAdded, FieldValue::Datetime(x)) => {
491                        attrs_map.insert(TrackAttrs::DateAdded, FieldValue::Datetime(x));
492                    }
493                    _ => {
494                        error!("failed to match: ({:#?}, {:#?})!", attr, field.value());
495                    }
496                },
497                None => {
498                    error!("failed to match: {}", field.id());
499                }
500            }
501        }
502
503        // TODO(sp1ff): This seems awful to me. I don't know if this is Rusty (Rustaceous?)
504        // build the track instance thus:
505        let filename = match attrs_map.get(&TrackAttrs::Filename) {
506            Some(FieldValue::Filename(x)) => x.clone(),
507            _ => {
508                return Err(Error::new(Cause::NoFilename));
509            }
510        };
511        // TODO(sp1ff): return an error if there is a field with the correct column id, but the
512        // wrong type!
513        let artist = match attrs_map.get(&TrackAttrs::Artist) {
514            Some(FieldValue::String(x)) => Some(x.clone()),
515            _ => None,
516        };
517        let title = match attrs_map.get(&TrackAttrs::Title) {
518            Some(FieldValue::String(x)) => Some(x.clone()),
519            _ => None,
520        };
521        let album = match attrs_map.get(&TrackAttrs::Album) {
522            Some(FieldValue::String(x)) => Some(x.clone()),
523            _ => None,
524        };
525        let year = match attrs_map.get(&TrackAttrs::Year) {
526            Some(FieldValue::Integer(x)) => Some(*x),
527            _ => None,
528        };
529        let genre = match attrs_map.get(&TrackAttrs::Genre) {
530            Some(FieldValue::String(x)) => Some(x.clone()),
531            _ => None,
532        };
533        let comment = match attrs_map.get(&TrackAttrs::Comment) {
534            Some(FieldValue::String(x)) => Some(x.clone()),
535            _ => None,
536        };
537        let trackno = match attrs_map.get(&TrackAttrs::TrackNo) {
538            Some(FieldValue::Integer(x)) => Some(*x),
539            _ => None,
540        };
541        let length = match attrs_map.get(&TrackAttrs::Length) {
542            Some(FieldValue::Integer(x)) => Some(*x),
543            _ => None,
544        };
545        let ttype = match attrs_map.get(&TrackAttrs::Type) {
546            Some(FieldValue::Integer(x)) => Some(*x),
547            _ => None,
548        };
549        let lastupd = match attrs_map.get(&TrackAttrs::LastUpd) {
550            Some(FieldValue::Datetime(x)) => Some(*x),
551            _ => None,
552        };
553        let lastplay = match attrs_map.get(&TrackAttrs::LastPlay) {
554            Some(FieldValue::Datetime(x)) => Some(*x),
555            _ => None,
556        };
557        let rating = match attrs_map.get(&TrackAttrs::Rating) {
558            Some(FieldValue::Integer(x)) => Some(*x),
559            _ => None,
560        };
561        let tuid2 = match attrs_map.get(&TrackAttrs::Tuid2) {
562            Some(FieldValue::String(x)) => Some(x.clone()),
563            _ => None,
564        };
565        let play_count = match attrs_map.get(&TrackAttrs::PlayCount) {
566            Some(FieldValue::Integer(x)) => Some(*x),
567            _ => None,
568        };
569
570        let filetime = match attrs_map.get(&TrackAttrs::Filetime) {
571            Some(FieldValue::Integer(x)) => Some(*x),
572            _ => None,
573        };
574        let filesize = match attrs_map.get(&TrackAttrs::Filesize) {
575            Some(FieldValue::Int64(x)) => Some(*x),
576            _ => None,
577        };
578        let bitrate = match attrs_map.get(&TrackAttrs::Bitrate) {
579            Some(FieldValue::Integer(x)) => Some(*x),
580            _ => None,
581        };
582        let disc = match attrs_map.get(&TrackAttrs::Disc) {
583            Some(FieldValue::Integer(x)) => Some(*x),
584            _ => None,
585        };
586        let albumartist = match attrs_map.get(&TrackAttrs::Albumartist) {
587            Some(FieldValue::String(x)) => Some(x.clone()),
588            _ => None,
589        };
590        let replaygain_album_gain = match attrs_map.get(&TrackAttrs::ReplaygainAlbumGain) {
591            Some(FieldValue::String(x)) => Some(x.clone()),
592            _ => None,
593        };
594        let replaygain_track_gain = match attrs_map.get(&TrackAttrs::ReplaygainTrackGain) {
595            Some(FieldValue::String(x)) => Some(x.clone()),
596            _ => None,
597        };
598        let publisher = match attrs_map.get(&TrackAttrs::Publisher) {
599            Some(FieldValue::String(x)) => Some(x.clone()),
600            _ => None,
601        };
602        let composer = match attrs_map.get(&TrackAttrs::Composer) {
603            Some(FieldValue::String(x)) => Some(x.clone()),
604            _ => None,
605        };
606        let bpm = match attrs_map.get(&TrackAttrs::Bpm) {
607            Some(FieldValue::Integer(x)) => Some(*x),
608            _ => None,
609        };
610        let discs = match attrs_map.get(&TrackAttrs::Discs) {
611            Some(FieldValue::Integer(x)) => Some(*x),
612            _ => None,
613        };
614        let tracks = match attrs_map.get(&TrackAttrs::Tracks) {
615            Some(FieldValue::Integer(x)) => Some(*x),
616            _ => None,
617        };
618        let ispodcast = match attrs_map.get(&TrackAttrs::IsPodcast) {
619            Some(FieldValue::Integer(x)) => Some(*x),
620            _ => None,
621        };
622        let podcastchannel = match attrs_map.get(&TrackAttrs::PodcastChannel) {
623            Some(FieldValue::String(x)) => Some(x.clone()),
624            _ => None,
625        };
626        let podcastpubdate = match attrs_map.get(&TrackAttrs::PodcastPubdate) {
627            Some(FieldValue::Datetime(x)) => Some(*x),
628            _ => None,
629        };
630        let gracenote_file_id = match attrs_map.get(&TrackAttrs::GracenoteFileId) {
631            Some(FieldValue::String(x)) => Some(x.clone()),
632            _ => None,
633        };
634        let gracenote_ext_data = match attrs_map.get(&TrackAttrs::GracenoteExtData) {
635            Some(FieldValue::String(x)) => Some(x.clone()),
636            _ => None,
637        };
638        let lossless = match attrs_map.get(&TrackAttrs::Lossless) {
639            Some(FieldValue::Integer(x)) => Some(*x),
640            _ => None,
641        };
642        let category = match attrs_map.get(&TrackAttrs::Category) {
643            Some(FieldValue::String(x)) => Some(x.clone()),
644            _ => None,
645        };
646        let codec = match attrs_map.get(&TrackAttrs::Codec) {
647            Some(FieldValue::String(x)) => Some(x.clone()),
648            _ => None,
649        };
650        let director = match attrs_map.get(&TrackAttrs::Director) {
651            Some(FieldValue::String(x)) => Some(x.clone()),
652            _ => None,
653        };
654        let producer = match attrs_map.get(&TrackAttrs::Producer) {
655            Some(FieldValue::String(x)) => Some(x.clone()),
656            _ => None,
657        };
658        let width = match attrs_map.get(&TrackAttrs::Width) {
659            Some(FieldValue::Integer(x)) => Some(*x),
660            _ => None,
661        };
662        let height = match attrs_map.get(&TrackAttrs::Height) {
663            Some(FieldValue::Integer(x)) => Some(*x),
664            _ => None,
665        };
666        let mimetype = match attrs_map.get(&TrackAttrs::MimeType) {
667            Some(FieldValue::String(x)) => Some(x.clone()),
668            _ => None,
669        };
670        let dateadded = match attrs_map.get(&TrackAttrs::DateAdded) {
671            Some(FieldValue::Datetime(x)) => Some(*x),
672            _ => None,
673        };
674
675        Ok(Track {
676            filename: filename,
677            artist: artist,
678            title: title,
679            album: album,
680            year: year,
681            genre: genre,
682            comment: comment,
683            trackno: trackno,
684            length: length,
685            ttype: ttype,
686            lastupd: lastupd,
687            lastplay: lastplay,
688            rating: rating,
689            tuid2: tuid2,
690            play_count: play_count,
691            filetime: filetime,
692            filesize: filesize,
693            bitrate: bitrate,
694            disc: disc,
695            albumartist: albumartist,
696            replaygain_album_gain: replaygain_album_gain,
697            replaygain_track_gain: replaygain_track_gain,
698            publisher: publisher,
699            composer: composer,
700            bpm: bpm,
701            discs: discs,
702            tracks: tracks,
703            is_podcast: ispodcast,
704            podcast_channel: podcastchannel,
705            podcast_pubdate: podcastpubdate,
706            gracenote_file_id: gracenote_file_id,
707            gracenote_ext_data: gracenote_ext_data,
708            lossless: lossless,
709            category: category,
710            codec: codec,
711            director: director,
712            producer: producer,
713            width: width,
714            height: height,
715            mimetype: mimetype,
716            date_added: dateadded,
717        })
718    }
719}