Skip to main content

flac_tracksplit/
lib.rs

1use anyhow::{bail, Context};
2use int_conv::Truncate;
3use metaflac::{
4    block::{Picture, PictureType, StreamInfo, VorbisComment},
5    Block,
6};
7use more_asserts as ma;
8use std::{
9    borrow::Cow,
10    fmt::Debug,
11    fs::{create_dir_all, File},
12    io::Write,
13    num::NonZeroU32,
14    path::{Path, PathBuf},
15    str::FromStr,
16};
17use symphonia_bundle_flac::FlacReader;
18use symphonia_core::{
19    checksum::{Crc16Ansi, Crc8Ccitt},
20    formats::{Cue, FormatReader, Packet},
21    io::{MediaSourceStream, Monitor, ReadBytes},
22    meta::{StandardVisualKey, Tag, Value, Visual},
23};
24use tracing::{debug, info, instrument};
25
26#[instrument(skip(base_path, metadata_padding), err)]
27pub fn split_one_file<P: AsRef<Path> + Debug, B: AsRef<Path> + Debug>(
28    input_path: P,
29    base_path: B,
30    metadata_padding: u32,
31) -> anyhow::Result<Vec<PathBuf>> {
32    let file = File::open(&input_path).with_context(|| format!("opening {:?}", input_path))?;
33    let file_length = file.metadata().context("file metadata")?.len();
34    let mss = MediaSourceStream::new(Box::new(file), Default::default());
35    let mut reader =
36        FlacReader::try_new(mss, &Default::default()).context("could not create flac reader")?;
37    debug!("tracks: {:?}", reader.tracks());
38    let track = reader.default_track().context("no default track")?;
39    let data = match &track.codec_params.extra_data {
40        Some(it) => it,
41        _ => bail!("Unclear track codec params - Not a flac file?"),
42    };
43    let info = StreamInfo::from_bytes(data);
44    let cues = reader.cues().to_vec();
45    let time_base = track.codec_params.time_base.context("track time base")?;
46    if time_base.numer != 1 {
47        bail!(
48            "track time_base numerator should be a fraction like 1/44000, instead {:?}",
49            time_base
50        );
51    }
52    if time_base.denom != info.sample_rate {
53        bail!("track time_base denominator ({:?}) should be the same as the overall streaminfo ({:?})", time_base, info.sample_rate);
54    }
55    // since we're sure that the sample rate is an even denominator of
56    // symphonia's TimeBase, we can assume that the time stamps are in
57    // samples:
58    let last_ts: u64 = info.total_samples;
59
60    let mut track_paths = vec![];
61    let mut cue_iter = cues.iter().peekable();
62    let mut audio_buffer = Vec::with_capacity(file_length.try_into().unwrap());
63    while let Some(cue) = cue_iter.next() {
64        audio_buffer.clear();
65        let next = cue_iter.peek();
66        let end_ts = match next {
67            None => last_ts, // no lead-out, fudge it.
68            Some(track) if track.index == LEAD_OUT_TRACK_NUMBER => {
69                // we have a lead-out, capture the whole in the last track.
70                let end_ts = track.start_ts;
71                cue_iter.next();
72                end_ts
73            }
74            Some(track) => track.start_ts,
75        };
76        let track = {
77            let metadata = reader.metadata();
78            let current_metadata = metadata.current().context("track tags")?;
79            let tags = current_metadata.tags();
80            let visuals = current_metadata.visuals();
81            Track::from_tags(&info, cue, end_ts, tags, visuals)
82        };
83        debug!(number = track.number, output = ?track.pathname(), "Track");
84        let pathbuf = base_path.as_ref().join(track.pathname());
85        let path = &pathbuf;
86        if let Some(parent) = path.parent() {
87            create_dir_all(parent).context("creating album dir")?;
88        }
89        let mut f = File::create(path).unwrap();
90        let sample_count = track
91            .write_audio(&mut reader, &mut audio_buffer)
92            .with_context(|| format!("buffering track {:?} audio", path))?;
93
94        track
95            .write_metadata(sample_count, metadata_padding, &mut f)
96            .with_context(|| format!("writing track {:?}", path))?;
97        f.write_all(&audio_buffer)
98            .with_context(|| format!("writing track {:?} audio", path))?;
99        track_paths.push(pathbuf);
100    }
101    info!("Done with disc image");
102    Ok(track_paths)
103}
104
105/// The track number used to identify a lead-out track on a cue sheet.
106pub const LEAD_OUT_TRACK_NUMBER: u32 = 170;
107
108/// Metadata identifying a track in a FLAC file that has an embedded CUE sheet.
109#[derive(Clone)]
110pub struct Track {
111    streaminfo: StreamInfo,
112    pub number: u32,
113    pub start_ts: u64,
114    pub end_ts: u64,
115    pub tags: Vec<Tag>,
116    pub visuals: Vec<Visual>,
117}
118
119impl std::fmt::Debug for Track {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        f.debug_struct("Track")
122            .field("number", &self.number)
123            .field("start_ts", &self.start_ts)
124            .field("end_ts", &self.end_ts)
125            .field("tags", &self.tags)
126            .finish()
127    }
128}
129
130impl Track {
131    fn interesting_tag(name: &str) -> bool {
132        !name.ends_with(']') && name != "CUESHEET" && name != "LOG"
133    }
134
135    /// Create a [Track] from a file's embedded FLAC&vorbis comments and CUE sheet.
136    pub fn from_tags(
137        streaminfo: &StreamInfo,
138        cue: &Cue,
139        end_ts: u64,
140        tags: &[Tag],
141        visuals: &[Visual],
142    ) -> Self {
143        let suffix = format!("[{}]", cue.index);
144        let tags = tags
145            .iter()
146            .filter_map(|tag| {
147                let tag_name = if tag.key.ends_with(&suffix) {
148                    Some(&tag.key[0..(tag.key.len() - suffix.len())])
149                } else if Self::interesting_tag(&tag.key) {
150                    Some(tag.key.as_str())
151                } else {
152                    None
153                };
154                tag_name.map(|key| Tag::new(tag.std_key, key, tag.value.clone()))
155            })
156            .collect();
157        let visuals = visuals.to_vec();
158        Self {
159            streaminfo: StreamInfo {
160                md5: [0u8; 16].to_vec(),
161                total_samples: (end_ts - cue.start_ts),
162                ..streaminfo.clone()
163            },
164            number: cue.index,
165            start_ts: cue.start_ts,
166            end_ts,
167            tags,
168            visuals,
169        }
170    }
171
172    /// Return the tag value for a given tag name.
173    pub fn tag_value(&self, name: &str) -> Option<&Value> {
174        self.tags
175            .iter()
176            .find(|tag| tag.key == name)
177            .map(|found| &found.value)
178    }
179
180    fn is_risky_char(c: char) -> bool {
181        match c {
182            // question marks, single quotes, forward / backslashes
183            // and colons, shell escapey things are not safe; Picard
184            // chokes on them sadly.
185            ' ' | '_' | '-' | ',' | '.' | '!' | '&' | '(' | ')' | '[' | ']' | '{' | '}' | '<'
186            | '>' => false,
187            _ => !c.is_alphanumeric(),
188        }
189    }
190
191    fn sanitize_pathname(name: &str) -> Cow<str> {
192        if name.contains(Self::is_risky_char) {
193            Cow::Owned(name.replace(Self::is_risky_char, "_"))
194        } else {
195            Cow::Borrowed(name)
196        }
197    }
198
199    /// Return the output pathname for a track.
200    pub fn pathname(&self) -> PathBuf {
201        let mut buf = PathBuf::new();
202        if let Some(Value::String(artist)) = self.tag_value("ALBUMARTIST") {
203            buf.push(Self::sanitize_pathname(artist).as_ref());
204        } else if let Some(Value::String(artist)) = self.tag_value("ARTIST") {
205            buf.push(Self::sanitize_pathname(artist).as_ref());
206        } else {
207            buf.push("Unknown Artist");
208        }
209
210        if let Some(Value::String(album)) = self.tag_value("ALBUM") {
211            if let Some(Value::String(year)) = self.tag_value("DATE") {
212                buf.push(format!("{} - {}", year, Self::sanitize_pathname(album)));
213            } else {
214                buf.push(Self::sanitize_pathname(album).as_ref());
215            }
216        } else {
217            buf.push("Unknown Album");
218        }
219
220        if let (Some(Value::String(track)), Some(Value::String(title))) =
221            (self.tag_value("TRACKNUMBER"), self.tag_value("TITLE"))
222        {
223            if let Ok(trackno) = <usize as FromStr>::from_str(track) {
224                buf.push(format!(
225                    "{:02}.{}.flac",
226                    trackno,
227                    Self::sanitize_pathname(title)
228                ));
229            } else {
230                buf.push(format!("99.{}.flac", Self::sanitize_pathname(title)));
231            }
232        }
233        buf
234    }
235
236    /// Write a track's
237    /// [STREAM](https://xiph.org/flac/format.html#stream) metadata
238    /// blocks - first STREAMINFO, then the remainder containing
239    /// pictures and vorbis comments.
240    #[instrument(skip(self, to), fields(number = self.number, path = ?self.pathname()), err)]
241    pub fn write_metadata<S: Write>(
242        &self,
243        total_samples: u64,
244        metadata_padding: u32,
245        mut to: S,
246    ) -> anyhow::Result<()> {
247        to.write_all(b"fLaC")?;
248        let comment = VorbisComment {
249            vendor_string: "flac-tracksplit".to_string(),
250            comments: self
251                .tags
252                .iter()
253                .map(|tag| (tag.key.to_string(), vec![tag.value.to_string()]))
254                .collect(),
255        };
256        let pictures: Vec<Block> = self
257            .visuals
258            .iter()
259            .map(|visual| {
260                Block::Picture(Picture {
261                    picture_type: translate_visual_key(
262                        visual.usage.unwrap_or(StandardVisualKey::OtherIcon),
263                    ),
264                    mime_type: visual.media_type.to_string(),
265                    description: "".to_string(),
266                    width: visual.dimensions.map(|s| s.width).unwrap_or(0),
267                    height: visual.dimensions.map(|s| s.height).unwrap_or(0),
268                    depth: visual.bits_per_pixel.map(NonZeroU32::get).unwrap_or(0),
269                    num_colors: match visual.color_mode {
270                        Some(symphonia_core::meta::ColorMode::Discrete) => 0,
271                        Some(symphonia_core::meta::ColorMode::Indexed(n)) => n.get(),
272                        None => 0,
273                    },
274                    data: visual.data.to_vec(),
275                })
276            })
277            .collect();
278        let mut streaminfo = self.streaminfo.clone();
279        if total_samples != streaminfo.total_samples {
280            // This is a pretty peaceful condition (difference is
281            // about less than 1/10s), but let's let curious users
282            // know since it's the one thing that is "imprecise" about
283            // how this tool operates.
284            debug!(
285                inferred = streaminfo.total_samples,
286                actual = total_samples,
287                duration_diff_s = (streaminfo.total_samples as f32 - total_samples as f32)
288                    / (streaminfo.sample_rate as f32),
289                "inferred and actual total samples differ."
290            );
291        }
292        streaminfo.total_samples = total_samples;
293        let headers = vec![Block::StreamInfo(streaminfo), Block::VorbisComment(comment)];
294        for block in headers.into_iter().chain(pictures.into_iter()) {
295            block
296                .write_to(false, &mut to)
297                .with_context(|| format!("writing block {:?}", block))?;
298        }
299        Block::Padding(metadata_padding)
300            .write_to(true, &mut to)
301            .context("writing padding")?;
302        Ok(())
303    }
304
305    /// Write a STREAM's
306    /// [FRAME](https://xiph.org/flac/format.html#frame) sequence,
307    /// containing compressed audio samples. Returns the number of samples actually processed.
308    #[instrument(skip(self, from, to), fields(number = self.number, path = ?self.pathname()), err)]
309    pub fn write_audio<S: Write>(&self, from: &mut FlacReader, mut to: S) -> anyhow::Result<u64> {
310        // TODO: Seek to the track start. Currently, this is only
311        // called in sequence (we're parallel per-file), so no need to
312        // do that rn, but it would be nice!
313
314        let mut last_end: u64 = 0;
315        let mut frame = OffsetFrame::default();
316        loop {
317            let packet = from
318                .next_packet()
319                .with_context(|| format!("last end: {:?} vs {:?}", last_end, self.end_ts))?;
320
321            let ts = packet.ts;
322            let dur = packet.dur;
323            ma::assert_ge!(
324                ts,
325                self.start_ts,
326                "Packet timestamp is not >= this track's start ts. Potential bug exposed by the previous track.",
327            );
328
329            // Adjust the frame header:
330            // * Adjust sample/frame number such that each track starts at frame/sample 0. This should fix seeking.
331            // * Recompute the 8-bit header CRC
332            // * Recompute the 16-bit footer CRC
333
334            let updated_buf = frame
335                .process(packet)
336                .with_context(|| format!("processing frame at ts {}", ts))?;
337            to.write_all(&updated_buf)?;
338
339            last_end = ts + dur;
340            if last_end >= self.end_ts {
341                return Ok(frame.samples_processed);
342            }
343        }
344    }
345}
346
347fn translate_visual_key(key: StandardVisualKey) -> PictureType {
348    use PictureType::*;
349    match key {
350        StandardVisualKey::FileIcon => Icon,
351        StandardVisualKey::OtherIcon => Other,
352        StandardVisualKey::FrontCover => CoverFront,
353        StandardVisualKey::BackCover => CoverBack,
354        StandardVisualKey::Leaflet => Leaflet,
355        StandardVisualKey::Media => Media,
356        StandardVisualKey::LeadArtistPerformerSoloist => LeadArtist,
357        StandardVisualKey::ArtistPerformer => Artist,
358        StandardVisualKey::Conductor => Conductor,
359        StandardVisualKey::BandOrchestra => Band,
360        StandardVisualKey::Composer => Composer,
361        StandardVisualKey::Lyricist => Lyricist,
362        StandardVisualKey::RecordingLocation => RecordingLocation,
363        StandardVisualKey::RecordingSession => DuringRecording,
364        StandardVisualKey::Performance => DuringPerformance,
365        StandardVisualKey::ScreenCapture => ScreenCapture,
366        StandardVisualKey::Illustration => Illustration,
367        StandardVisualKey::BandArtistLogo => BandLogo,
368        StandardVisualKey::PublisherStudioLogo => PublisherLogo,
369    }
370}
371
372/// A FLAC stream's [Frame](https://xiph.org/flac/format.html#frame),
373/// with samples that are offset such that the first frame has a
374/// frame/sample offset of 0 and the others follow suit.
375///
376/// This is meant to be created once per [Track], and then updated for
377/// all the frames making up that track.
378#[derive(Default)]
379pub struct OffsetFrame {
380    initial_offset: Option<u64>,
381    samples_processed: u64,
382}
383
384impl OffsetFrame {
385    /// Processes a FLAC frame by rewriting its sample/frame offset
386    /// and CRC checksums, and emits that frame in an updated byte
387    /// buffer.
388    ///
389    /// Returns a byte buffer containing the updated frame.
390    pub fn process(&mut self, packet: Packet) -> anyhow::Result<Vec<u8>> {
391        let mut frame_reader = packet.as_buf_reader();
392        let mut header_crc = Crc8Ccitt::new(0);
393        let mut footer_crc = Crc16Ansi::new(0);
394        let mut frame_out = Vec::with_capacity(packet.buf().len());
395
396        // FLAC frame magic number / reserved bits
397        let sync = frame_reader.read_be_u16().context("reading frame sync")?;
398        let sync_u8 = sync.to_be_bytes();
399        header_crc.process_double_bytes(sync_u8);
400        footer_crc.process_double_bytes(sync_u8);
401        frame_out.write_all(&sync_u8)?;
402
403        // FLAC frame description
404        let desc = frame_reader.read_be_u16().context("reading frame desc")?;
405        let desc_u8 = desc.to_be_bytes();
406        header_crc.process_double_bytes(desc_u8);
407        footer_crc.process_double_bytes(desc_u8);
408        frame_out.write_all(&desc_u8)?;
409        let block_size_enc = u32::from((desc & 0xf000) >> 12);
410        let sample_rate_enc = u32::from((desc & 0x0f00) >> 8);
411
412        // Next up is the frame/sample number, here we munge some data:
413        let (orig_sample_offset, _sample_n_bytes) =
414            utf8_decode_be_u64(&mut frame_reader).context("decoding the sample offset")?;
415        if self.initial_offset.is_none() {
416            debug!(orig_sample_offset, "first sample offset");
417            self.initial_offset.replace(orig_sample_offset);
418        }
419        let sample_offset = orig_sample_offset - self.initial_offset.unwrap();
420        let offset_u8 = utf8_encode_be_u64(sample_offset).context("encoding the new offset")?;
421        header_crc.process_buf_bytes(&offset_u8);
422        footer_crc.process_buf_bytes(&offset_u8);
423        frame_out.write_all(&offset_u8)?;
424
425        // Now, some gymnastics to read the sample rate & block size
426        // if necessary (behavior dictated by the appropriate bits in
427        // the `desc` fields).
428        let block_samples: u64 = match block_size_enc & 0b1111 {
429            0b0110 => {
430                // block size is given in the next 8 bits:
431                let bs_u8 = frame_reader.read_u8().context("8bit block size")?;
432                header_crc.process_byte(bs_u8);
433                footer_crc.process_byte(bs_u8);
434                frame_out.write_all(&[bs_u8])?;
435                bs_u8.into()
436            }
437            0b0111 => {
438                // block size given in the next 16 bits:
439                let bs = frame_reader.read_be_u16().context("8bit block size")?;
440                let bs_u8 = bs.to_be_bytes();
441                header_crc.process_double_bytes(bs_u8);
442                footer_crc.process_double_bytes(bs_u8);
443                frame_out.write_all(&bs_u8)?;
444                bs.into()
445            }
446            0b0001 => 192,
447            0b0000 => bail!("reserved sample count"),
448            b if b & 0b1000 != 0 => 256 * 2u64.pow((b & 0b1111) - 8),
449            b => 576 * 2u64.pow((b & 0b111) - 2),
450        };
451        match sample_rate_enc & 0b1111 {
452            0b1100 => {
453                // sample rate is given in the next 8 bits:
454                let sr_u8 = frame_reader.read_u8().context("8bit block size")?;
455                header_crc.process_byte(sr_u8);
456                footer_crc.process_byte(sr_u8);
457                frame_out.write_all(&[sr_u8])?;
458            }
459            0b1101 | 0b1110 => {
460                // sample rate given in the next 16 bits:
461                let bs = frame_reader.read_be_u16().context("8bit block size")?;
462                let sr_u8 = bs.to_be_bytes();
463                header_crc.process_double_bytes(sr_u8);
464                footer_crc.process_double_bytes(sr_u8);
465                frame_out.write_all(&sr_u8)?;
466            }
467            0b1111 => anyhow::bail!("invalid sample rate: sync-fooling string of 1s"),
468            _ => {
469                // No bits used for the field otherwise
470            }
471        }
472
473        // What follows is the header CRC. We write out the one computed above:
474        let _original_header_crc = frame_reader.read_u8().context("reading header CRC")?;
475        let my_header_crc = header_crc.crc();
476        footer_crc.process_byte(my_header_crc);
477        frame_out.write_all(&[my_header_crc])?;
478
479        // Next, the subframes; we do not touch them, but we do rewrite the footer CRC:
480        let remainder = frame_reader.read_buf_bytes_available_ref();
481        let subframes = &remainder[..remainder.len() - 2];
482        footer_crc.process_buf_bytes(subframes);
483        frame_out.write_all(subframes)?;
484
485        let my_footer_crc = footer_crc.crc();
486        let my_footer_crc_u8 = my_footer_crc.to_be_bytes();
487        frame_out.write_all(&my_footer_crc_u8)?;
488        self.samples_processed += block_samples;
489        Ok(frame_out)
490    }
491}
492
493/// Decodes a big-endian unsigned integer encoded via extended UTF8. In this context, extended UTF8
494/// simply means the encoded UTF8 value may be up to 7 bytes for a maximum integer bit width of
495/// 36-bits. Returns the number of bytes that contain the encoded number as the second value.
496///
497// Taken from symphonia.
498fn utf8_decode_be_u64<B: symphonia_core::io::ReadBytes>(src: &mut B) -> anyhow::Result<(u64, u32)> {
499    // Read the first byte of the UTF8 encoded integer.
500    let mut state = u64::from(src.read_u8()?);
501
502    // UTF8 prefixes 1s followed by a 0 to indicate the total number of bytes within the multi-byte
503    // sequence. Using ranges, determine the mask that will overlap the data bits within the first
504    // byte of the sequence. For values 0-128, return the value immediately. If the value falls out
505    // of range return None as this is either not the start of a UTF8 sequence or the prefix is
506    // incorrect.
507    let mask: u8 = match state {
508        0x00..=0x7f => return Ok((state, 1)),
509        0xc0..=0xdf => 0x1f,
510        0xe0..=0xef => 0x0f,
511        0xf0..=0xf7 => 0x07,
512        0xf8..=0xfb => 0x03,
513        0xfc..=0xfd => 0x01,
514        0xfe => 0x00,
515        _ => anyhow::bail!("invalid utf-8 encoded sample/frame number"),
516    };
517
518    // Obtain the data bits from the first byte by using the data mask.
519    state &= u64::from(mask);
520
521    // Read the remaining bytes within the UTF8 sequence. Since the mask 0s out the UTF8 prefix
522    // of 1s which indicate the length of the multi-byte sequence in bytes, plus an additional 0
523    // bit, the number of remaining bytes to read is the number of zeros in the mask minus 2.
524    // To avoid extra computation, simply loop from 2 to the number of zeros.
525    for _i in 2..mask.leading_zeros() {
526        // Each subsequent byte after the first in UTF8 is prefixed with 0b10xx_xxxx, therefore
527        // only 6 bits are useful. Append these six bits to the result by shifting the result left
528        // by 6 bit positions, and appending the next subsequent byte with the first two high-order
529        // bits masked out.
530        state = (state << 6) | u64::from(src.read_u8()? & 0x3f);
531
532        // TODO: Validation? Invalid if the byte is greater than 0x3f.
533    }
534
535    Ok((state, mask.leading_zeros() - 1))
536}
537
538fn utf8_encode_be_u64(input: u64) -> anyhow::Result<Vec<u8>> {
539    let mut number = input;
540    let start_mask: u8 = match number.leading_zeros() {
541        57..=64 => return Ok(vec![number.truncate()]),
542        53..=56 => 0b1100_0000,
543        48..=52 => 0b1110_0000,
544        44..=47 => 0b1111_0000,
545        39..=43 => 0b1111_1000,
546        34..=38 => 0b1111_1100,
547        29..=33 => 0b1111_1110,
548        00..=28 | 65..=u32::MAX => unreachable!("can't encode more than 7 leading bits"),
549    };
550    debug_assert!(
551        64 - input.leading_zeros()
552            <= (start_mask.count_ones() - 1) * 6 + (start_mask.count_zeros() - 1),
553        "Number with {} set bits ({:b}) to be represented in {} bits",
554        64 - input.leading_zeros(),
555        input,
556        (start_mask.count_ones() - 1) * 6 + (start_mask.count_zeros() - 1)
557    );
558    let len: usize = start_mask.count_ones() as usize;
559    debug!(
560        len,
561        start_mask,
562        leading_zeros = input.leading_zeros(),
563        "allocating a vec for mask",
564    );
565    let mut val = vec![0; len];
566    let mut posn = len - 1;
567    while posn > 0 {
568        let byte: u8 = number.truncate();
569        val[posn] = (byte & 0x3f) | 0b10000000;
570        number >>= 6;
571        posn -= 1;
572    }
573    let byte: u8 = number.truncate();
574    val[0] = byte | start_mask;
575    debug_assert_eq!(
576        0,
577        byte >> (start_mask.count_zeros() - 1) as usize,
578        "Should have 0 set bits left"
579    );
580    Ok(val)
581}
582
583#[cfg(test)]
584mod test {
585    use super::*;
586    use proptest::{prop_assert_eq, proptest};
587    use std::fmt;
588    use symphonia_core::io::BufReader;
589
590    struct V<'a>(&'a [u8]);
591
592    impl<'a> fmt::Binary for V<'a> {
593        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
594            // extract the value using tuple idexing
595            // and create reference to 'vec'
596            let vec = &self.0;
597
598            // @count -> the index of the value,
599            // @n     -> the value
600            for (count, n) in vec.iter().enumerate() {
601                if count != 0 {
602                    write!(f, " ")?;
603                }
604
605                write!(f, "{:b}", n)?;
606            }
607
608            Ok(())
609        }
610    }
611
612    proptest! {
613        #[test]
614        fn test_encoding(input in 0..(2u64.pow(35))) {
615            let encoded = utf8_encode_be_u64(input).expect("encoding");
616            let mut buf = BufReader::new(&encoded);
617            let decoded = utf8_decode_be_u64(&mut buf).unwrap_or_else(|_| panic!("decoding {:b}", V(&encoded)));
618            prop_assert_eq!((input, encoded.len() as u32), decoded,
619                "received:\n{:#064b} but wanted:\n{:#064b}",
620                decoded.0, input);
621        }
622    }
623}